## TradeUtil
##
## Utilities for dealing with Trades and TradeData.
##
## Trading Partners
##
##   canTrade(playerOrID, withPlayerOrID)
##     Returns True if <player> can open the trade window with <withPlayer>.
##
##   getTechTradePartners(playerOrID)
##     Returns a list of CyPlayers that can trade technologies with <player>.
##
##   getBonusTradePartners(playerOrID)
##     Returns a list of CyPlayers that can trade bonuses with <player>.
##
##   getGoldTradePartners(playerOrID)
##     Returns a list of CyPlayers that can trade gold with <player>.
##
##   getMapTradePartners(playerOrID)
##     Returns a list of CyPlayers that can trade maps with <player>.
##
##
##   getOpenBordersTradePartners(playerOrID)
##     Returns a list of CyPlayers that can sign an Open Borders agreement with <player>.
##
##   getDefensivePactTradePartners(playerOrID)
##     Returns a list of CyPlayers that can sign a Defensive Pact with <player>.
##
##   getPermanentAllianceTradePartners(playerOrID)
##     Returns a list of CyPlayers that can sign a Permanent Alliance with <player>.
##
##
##   getPeaceTradePartners(playerOrID)
##     Returns a list of CyPlayers that can sign a peace treaty with <player>.
##
##   getVassalTradePartners(playerOrID)
##     Returns a list of CyPlayers that can become a vassal of <player>.
##
##   getCapitulationTradePartners(playerOrID)
##     Returns a list of CyPlayers that can capitulate to <player>.
##
##
##   tradeParters(playerOrID)
##     Iterates over all of <player>'s possible trade partners, yielding each CyPlayer in turn.
##
##   getTradePartnersByPlayer(playerOrID, testFunction, args...)
##     Returns a list of CyPlayers that can trade with <player>.
##
##   getTradePartnersByTeam(playerOrID, testFunction, args...)
##     Returns a list of CyPlayers that can trade with <player>.
##
## Trade Items
##
##   getDesiredBonuses(playerOrID)
##     Returns a set of bonus IDs that <player> can receive in trade.
##
##   getCorporationBonuses(playerOrID)
##     Returns the set of bonus IDs that <player> can receive due to their corporations.
##
##   getSurplusBonuses(playerOrID, minimum=1
##     Returns a list of bonus IDs of which <player> has at least <minimum>.
##
##   getTradeableBonuses(fromPlayerOrID, toPlayerOrID)
##     Returns two sets of bonus IDs that <fromPlayer> will and won't trade to <toPlayer>.
##
## Trade Routes
##
##   isFractionalTrade()
##     Returns True of BULL is active with Fractional Trade Routes.
##
##   getTradeProfitFunc()
##     Returns the CyCity function to use to calculate the trade route profit for a single route.
##
##   calculateTradeRouteYield(city, route, yieldType)
##     Returns the total <yieldType> for the <route>th trade route in <city>.
##
##   calculateTotalTradeRouteYield(city, yieldType)
##     Returns the total <yieldType> for all trade routes in <city>.
##
##   calculateTradeRoutes(playerOrID, withPlayerOrID=None)
##     Returns the domestic and foreign trade route yields and counts for <playerOrID>:
##     domestic yield, domestic count, foreign yield, and foreign count.
##     If <withPlayerOrID> is given, only counts trade routes to their cities.
##
## TradeData
##
##   format(player or ID, TradeData(s))
##     Returns a plain text description of the given tradeable item(s).
##
##   Trade(ePlayer, eOtherPlayer)
##     Can be used to create new trades.
##     (not really since implementDeal() not exposed to Python)
##
## Notes
##   - Must be initialized externally by calling init()
##
## Copyright (c) 2008 The BUG Mod.
##
## Author: EmperorFool

from CvPythonExtensions import *
import BugUtil
import DiplomacyUtil
import GameUtil
import PlayerUtil

# BUG - Mac Support - start
BugUtil.fixSets(globals())
# BUG - Mac Support - end

gc = CyGlobalContext()

CORP_BONUSES = {}

DOMESTIC_TRADE = 0
DOMESTIC_OVERSEAS_TRADE = 1
FOREIGN_TRADE = 2
FOREIGN_OVERSEAS_TRADE = 3

MAX_TRADE_ROUTES = gc.getDefineINT("MAX_TRADE_ROUTES")
FRACTIONAL_TRADE = False
TRADE_PROFIT_FUNC = None

TRADE_FORMATS = {}


## Trading Partners

def canTrade(playerOrID, withPlayerOrID):
	"""
	Returns True if <player> can open the trade window with <withPlayer>.
	"""
	return DiplomacyUtil.canContact(playerOrID, withPlayerOrID) and DiplomacyUtil.isWillingToTalk(withPlayerOrID, playerOrID)

def getTechTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can trade technologies with <player>.
	"""
	if not GameUtil.isTechTrading():
		return ()
	return getTradePartnersByTeam(playerOrID, lambda fromTeam, toTeam: fromTeam.isTechTrading() or toTeam.isTechTrading())

def getBonusTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can trade bonuses with <player>.
	"""
	return getTradePartnersByPlayer(playerOrID, lambda fromPlayer, toPlayer: fromPlayer.canTradeNetworkWith(toPlayer.getID()))

def getGoldTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can trade gold with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, lambda fromTeam, toTeam: fromTeam.isGoldTrading() or toTeam.isGoldTrading())

def getMapTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can trade maps with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, lambda fromTeam, toTeam: fromTeam.isMapTrading() or toTeam.isMapTrading())

def getOpenBordersTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign an Open Borders agreement with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canSignOpenBorders)

def canSignOpenBorders(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can sign an Open Borders agreement.
	"""
	if fromTeam.isOpenBorders(toTeam.getID()) or toTeam.isOpenBorders(fromTeam.getID()):
		return False
	return fromTeam.isOpenBordersTrading() or toTeam.isOpenBordersTrading()
#Afforess Advanced Diplomacy
def getLimitedBordersTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign an Limited Borders agreement with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canSignLimitedBorders)

def canSignLimitedBorders(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can sign an Limited Borders agreement. 
	Limited Borders are enabled when Open Borders are, so the functions are identical.
	"""
	if fromTeam.isOpenBorders(toTeam.getID()) or toTeam.isOpenBorders(fromTeam.getID()):
		return False
	if not (gc.getGame().isOption(GameOptionTypes.GAMEOPTION_ADVANCED_DIPLOMACY)):
		return False
	return fromTeam.isOpenBordersTrading() or toTeam.isOpenBordersTrading()

def getEmbassyTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign an Limited Borders agreement with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canTradeEmbassy)

def canTradeEmbassy(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can trade for an Embassy. 
	Embassies are enabled when Open Borders are, so the functions are identical.
	"""
	if fromTeam.isOpenBorders(toTeam.getID()) or toTeam.isOpenBorders(fromTeam.getID()):
		return False
	if not (gc.getGame().isOption(GameOptionTypes.GAMEOPTION_ADVANCED_DIPLOMACY)):
		return False
	return fromTeam.isOpenBordersTrading() or toTeam.isOpenBordersTrading()

#Afforess End
def getDefensivePactTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign a Defensive Pact with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canSignDefensivePact)

def canSignDefensivePact(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can sign a Defensive Pact.
	"""
	if fromTeam.isDefensivePact(toTeam.getID()) or toTeam.isDefensivePact(fromTeam.getID()):
		return False
	return fromTeam.isDefensivePactTrading() or toTeam.isDefensivePactTrading()

def getPermanentAllianceTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign a Permanent Alliance with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canSignPermanentAlliance)

def canSignPermanentAlliance(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can sign a Permanent Alliance.
	"""
	if fromTeam.getID() == toTeam.getID():
		return False
	return fromTeam.isPermanentAllianceTrading() or toTeam.isPermanentAllianceTrading()

def getPeaceTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can sign a peace treaty with <player>.
	"""
	return getTradePartnersByTeam(playerOrID, lambda fromTeam, toTeam: toTeam.isAtWar(fromTeam.getID()))

def getVassalTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can become a vassal of <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canAcceptVassal, False)

def getCapitulationTradePartners(playerOrID):
	"""
	Returns a list of CyPlayers that can capitulate to <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canAcceptVassal, True)

def canAcceptVassal(masterTeam, vassalTeam, bAtWar):
	"""
	Returns True if <vassalTeam> can become a vassal of <masterTeam>.
	
	Pass True for <bAtWar> to test for capitulation and False to test for peaceful vassalage.
	"""
	if masterTeam.getID() == vassalTeam.getID():
		return False
	if masterTeam.isAVassal() or vassalTeam.isAVassal():
		return False
	if masterTeam.isAtWar(vassalTeam.getID()) != bAtWar:
		return False
	# master must possess tech
	return masterTeam.isVassalStateTrading()
	
#RevolutionDCM start - extra civlerts
def getWarTraders(playerOrID):
	"""
	Returns a list of CyPlayers that can trade for war <player>.
	"""
	return getTradePartnersByTeam(playerOrID, canWarTrade)

def canWarTrade(fromTeam, toTeam):
	"""
	Returns True if the two CyTeams can trade for war
	"""
	if fromTeam.getID() == toTeam.getID():
		return False
	if fromTeam.isAVassal() or toTeam.isAVassal():
		return False
	if fromTeam.isAtWar(toTeam.getID()): # being at war means NO_DENIAL warTrade
		return False
	return True
#RevolutionDCM end	

def tradeParters(playerOrID):
	"""
	Iterates over all of <player>'s possible trade partners, yielding each CyPlayer in turn.
	"""
	player = PlayerUtil.getPlayer(playerOrID)
	for partner in PlayerUtil.players(alive=True, barbarian=False, minor=False):
		if canTrade(player, partner):
			yield partner

def getTradePartnersByPlayer(playerOrID, testFunction, *args):
	"""
	Returns a list of CyPlayers that can trade with <player>.
	
	<testFunction> is passed two CyPlayers plus <args> for each viable pairing and should return a boolean value.
	"""
	player = PlayerUtil.getPlayer(playerOrID)
	partners = []
	for partner in tradeParters(player):
		if testFunction(player, partner, *args):
			partners.append(partner)
	return partners

def getTradePartnersByTeam(playerOrID, testFunction, *args):
	"""
	Returns a list of CyPlayers that can trade with <player>.
	
	<testFunction> is passed two CyTeams plus <args> for each viable pairing and should return a boolean value.
	"""
	player = PlayerUtil.getPlayer(playerOrID)
	team = PlayerUtil.getTeam(player.getTeam())
	partners = []
	for partner in tradeParters(player):
		if testFunction(team, PlayerUtil.getTeam(partner.getTeam()), *args):
			partners.append(partner)
	return partners


## Trade Items

def getDesiredBonuses(playerOrID):
	"""
	Returns a set of bonus IDs that <player> can receive in trade.
	"""
	player, team = PlayerUtil.getPlayerAndTeam(playerOrID)
	bonuses = set()
	for eBonus in range(gc.getNumBonusInfos()):
		if player.getNumAvailableBonuses(eBonus) == 0:
			eObsoleteTech = gc.getBonusInfo(eBonus).getTechObsolete()
			if eObsoleteTech == -1 or not team.isHasTech(eObsoleteTech):
				bonuses.add(eBonus)
	return bonuses | getCorporationBonuses(player)

def getCorporationBonuses(playerOrID):
	"""
	Returns the set of bonus IDs that <player> can receive due to their corporations.
	
	Takes into account anything (e.g. civics) that alters <player>'s ability to run corporations.
	"""
	player = PlayerUtil.getPlayer(playerOrID)
	bonuses = set()
	for eCorp, inputs in CORP_BONUSES.iteritems():
		if player.getHasCorporationCount(eCorp) > 0:
			bonuses |= inputs
	return bonuses

def initCorporationBonuses():
	"""
	Initializes the CORP_BONUSES dictionary that maps each corporation ID to the set of bonus IDs it uses.
	"""
	for eCorp in range(gc.getNumCorporationInfos()):
		corp = gc.getCorporationInfo(eCorp)
		bonuses = set()
		for i in range(gc.getNUM_CORPORATION_PREREQ_BONUSES()):
			eBonus = corp.getPrereqBonus(i)
			if eBonus != -1:
				bonuses.add(eBonus)
		CORP_BONUSES[eCorp] = bonuses

def getSurplusBonuses(playerOrID, minimum=1):
	"""
	Returns a list of bonus IDs of which <player> has at least <minimum> available to export.
	"""
	player = PlayerUtil.getPlayer(playerOrID)
	available = []
	for eBonus in range(gc.getNumBonusInfos()):
		if player.getNumTradeableBonuses(eBonus) >= minimum:
			available.append(eBonus)
	return available

def getTradeableBonuses(fromPlayerOrID, toPlayerOrID):
	"""
	Returns two sets of bonus IDs that <fromPlayer> will and won't trade to <toPlayer>.
	
	Assumes that the two players can trade bonuses.
	"""
	fromPlayer = PlayerUtil.getPlayer(fromPlayerOrID)
	eToPlayer = PlayerUtil.getPlayerID(toPlayerOrID)
	fromPlayerIsHuman = fromPlayer.isHuman()
	will = set()
	wont = set()
	tradeData = TradeData()
	tradeData.ItemType = TradeableItems.TRADE_RESOURCES
	for eBonus in range(gc.getNumBonusInfos()):
		tradeData.iData = eBonus
		if fromPlayer.canTradeItem(eToPlayer, tradeData, False):
			if fromPlayerIsHuman or fromPlayer.canTradeItem(eToPlayer, tradeData, True):
				will.add(eBonus)
			else:
				wont.add(eBonus)
	return will, wont


## Trade Routes

def isFractionalTrade():
	"""
	Returns True of BULL is active with Fractional Trade Routes.
	"""
	return FRACTIONAL_TRADE

def getTradeProfitFunc():
	"""
	Returns the CyCity function to use to calculate the trade route profit for a single route.
	"""
	return TRADE_PROFIT_FUNC

def calculateTradeRouteYield(city, route, yieldType):
	"""
	Returns the total <yieldType> for the <route>th trade route in <city>.
	
	If Fractional Trade Routes is active, the value returned is fractional (times 100).
	"""
	return city.calculateTradeYield(yieldType, TRADE_PROFIT_FUNC(city, city.getTradeCity(route)))

def calculateTotalTradeRouteYield(city, yieldType):
	"""
	Returns the total <yieldType> for all trade routes in <city>.
	
	If Fractional Trade Routes is active, the total is rounded down and returned as a regular number.
	"""
	trade = 0
	for route in range(city.getTradeRoutes()):
		trade += calculateTradeRouteYield(city, route, yieldType)
	if isFractionalTrade():
		trade /= 100
	return trade
		
	return city.calculateTradeYield(yieldType, TRADE_PROFIT_FUNC(city, city.getTradeCity(route)))

def calculateTradeRoutes(playerOrID, withPlayerOrID=None):
	"""
	Returns the domestic and foreign trade route yields and counts for <playerOrID>:
	domestic yield, domestic count, foreign yield, and foreign count.
	
	If <withPlayerOrID> is given, only counts trade routes to their cities.
	If Fractional Trade Routes is active, the value returned is fractional (times 100).
	"""
	domesticTrade = domesticCount = foreignTrade = foreignCount = 0
	eTeam = PlayerUtil.getPlayerTeam(playerOrID)
	eWithPlayer = PlayerUtil.getPlayerID(withPlayerOrID)
	for city in PlayerUtil.playerCities(playerOrID):
		for i in range(city.getTradeRoutes()):
			tradeCity = city.getTradeCity(i)
			if tradeCity and tradeCity.getOwner() >= 0 and (eWithPlayer == -1 or eWithPlayer == tradeCity.getOwner()):
				trade = city.calculateTradeYield(YieldTypes.YIELD_COMMERCE, TRADE_PROFIT_FUNC(city, tradeCity))
				if tradeCity.getTeam() == eTeam:
					domesticTrade += trade
					domesticCount += 1
				else:
					foreignTrade += trade
					foreignCount += 1
	return domesticTrade, domesticCount, foreignTrade, foreignCount

def initFractionalTrade():
	"""
	Sets the global fractional trade constants by testing for the function it adds.
	
	Fractional Trade is an optional compile-time feature of BULL.
	"""
	global FRACTIONAL_TRADE, TRADE_PROFIT_FUNC
	try:
		TRADE_PROFIT_FUNC = CyCity.calculateTradeProfitTimes100
		FRACTIONAL_TRADE = True
		BugUtil.debug("TradeUtil - Fractional Trade Routes is active")
	except:
		TRADE_PROFIT_FUNC = CyCity.calculateTradeProfit
		FRACTIONAL_TRADE = False


## Trade Class

class Trade(object):
	"""
	Encapsulates the player IDs and TradeData for a new or proposed trade.
	
	Implements the same interface as the DealUtil.Deal class.
	"""
	def __init__(self, ePlayer, eOtherPlayer):
		self.ePlayer = ePlayer
		self.eOtherPlayer = eOtherPlayer
		self.tradeList = []
		self.otherTradeList = []
	
	def isReversed(self):
		return False
	def getPlayer(self):
		return self.ePlayer
	def getOtherPlayer(self):
		return self.eOtherPlayer
	
	def getCount(self):
		return len(self.tradeList)
	def getOtherCount(self):
		return len(self.otherTradeList)
	def getTrade(self, index):
		return self.tradeList[index]
	def getOtherTrade(self, index):
		return self.otherTradeList[index]
	def trades(self):
		return self.tradeList
	def otherTrades(self):
		return self.otherTradeList
	
	def addTrade(self, trade):
		self.tradeList.append(trade)
	def addOtherTrade(self, trade):
		self.otherTradeList.append(trade)
	
	def hasType(self, type):
		return self.hasAnyType((type,))
	def hasAnyType(self, types):
		for trade in self.trades():
			if trade.ItemType in types:
				return True
		for trade in self.otherTrades():
			if trade.ItemType in types:
				return True
		return False
	def findType(self, type):
		return self.findTypes((type,))
	def findTypes(self, types):
		found = []
		for trade in self.trades():
			for type in types:
				if type == trade.ItemType:
					found.append(trade)
		for trade in self.otherTrades():
			for type in types:
				if type == trade.ItemType:
					found.append(trade)
		return found
	
	def __repr__(self):
		return ("<trade %d [%s] for %d [%s]>" % 
				(self.getPlayer(), 
				format(self.getPlayer(), self.trades()), 
				self.getOtherPlayer(), 
				format(self.getOtherPlayer(), self.otherTrades())))


## TradeData Formatting

def format(player, trade):
	"""Returns a single string containing all of the trade items separated by commas.
	
	player can be either an ID or CyPlayer and is needed when a city is being traded.
	"""
	if isinstance(trade, list) or isinstance(trade, tuple) or isinstance(trade, set):
		return ", ".join([format(player, t) for t in trade])
	elif trade.ItemType in TRADE_FORMATS:
		return TRADE_FORMATS[trade.ItemType].format(player, trade)
	else:
		BugUtil.warn("TradeUtil - unknown item type %d", trade.ItemType)
		return ""

def initTradeableItems():
	addSimpleTrade("gold", TradeableItems.TRADE_GOLD, "TXT_KEY_TRADE_GOLD_NUM")
	addSimpleTrade("gold per turn", TradeableItems.TRADE_GOLD_PER_TURN, "TXT_KEY_TRADE_GOLD_PER_TURN_NUM")
	addPlainTrade("map", TradeableItems.TRADE_MAPS, "TXT_KEY_TRADE_WORLD_MAP_STRING")
	addPlainTrade("vassal", TradeableItems.TRADE_VASSAL, "TXT_KEY_TRADE_VASSAL_TREATY_STRING")
	addPlainTrade("capitulation", TradeableItems.TRADE_SURRENDER, "TXT_KEY_TRADE_CAPITULATE_STRING")
	addPlainTrade("open borders", TradeableItems.TRADE_OPEN_BORDERS, "TXT_KEY_TRADE_OPEN_BORDERS_STRING")
	addPlainTrade("defensive pact", TradeableItems.TRADE_DEFENSIVE_PACT, "TXT_KEY_TRADE_DEFENSIVE_PACT_STRING")
	addPlainTrade("alliance", TradeableItems.TRADE_PERMANENT_ALLIANCE, "TXT_KEY_TRADE_PERMANENT_ALLIANCE_STRING")
	addComplexTrade("peace treaty", TradeableItems.TRADE_PEACE_TREATY, getTradePeaceDeal)
	addComplexTrade("technology", TradeableItems.TRADE_TECHNOLOGIES, getTradeTech)
	addComplexTrade("resource", TradeableItems.TRADE_RESOURCES, getTradeBonus)
	addComplexTrade("city", TradeableItems.TRADE_CITIES, getTradeCity)
	addAppendingTrade("peace", TradeableItems.TRADE_PEACE, "TXT_KEY_TRADE_PEACE_WITH", getTradePlayer)
	addAppendingTrade("war", TradeableItems.TRADE_WAR, "TXT_KEY_TRADE_WAR_WITH", getTradePlayer)
	addAppendingTrade("trade embargo", TradeableItems.TRADE_EMBARGO, "TXT_KEY_TRADE_STOP_TRADING_WITH", getTradePlayer, " %s")
	addAppendingTrade("civic", TradeableItems.TRADE_CIVIC, "TXT_KEY_TRADE_ADOPT", getTradeCivic)
	addAppendingTrade("religion", TradeableItems.TRADE_RELIGION, "TXT_KEY_TRADE_CONVERT", getTradeReligion)
	#Afforess
	addPlainTrade("limited borders", TradeableItems.TRADE_RIGHT_OF_PASSAGE, "TXT_KEY_TRADE_LIMITED_BORDERS_STRING")
	addPlainTrade("embassy", TradeableItems.TRADE_EMBASSY, "TXT_KEY_TRADE_EMBASSY_STRING")
	addComplexTrade("units", TradeableItems.TRADE_MILITARY_UNIT, getTradeUnits)
	addPlainTrade("non-aggression", TradeableItems.TRADE_NON_AGGRESSION, "TXT_KEY_TRADE_NON_AGGRESSION")
	##addPlainTrade("pow exchange", TradeableItems.TRADE_POW, "TXT_KEY_TRADE_POW")
	addPlainTrade("free trade zone", TradeableItems.TRADE_FREE_TRADE_ZONE, "TXT_KEY_TRADE_FREE_TRADE_ZONE")
	##addPlainTrade("war reparations", TradeableItems.TRADE_WAR_REPARATIONS, "TXT_KEY_TRADE_WAR_REPARATIONS")
	addComplexTrade("prepare for war", TradeableItems.TRADE_WAR_PREPARE, getTradePlayer)
	addPlainTrade("worker", TradeableItems.TRADE_WORKER, "TXT_KEY_TRADE_WORKER")
	addComplexTrade("contact", TradeableItems.TRADE_CONTACT, getTradePlayer)
	#Afforess End
	
def addPlainTrade(name, type, key):
	"""Creates a trade using an unparameterized XML <text> tag."""
	return addTrade(type, PlainTradeFormat(name, type, key))

def addSimpleTrade(name, type, key):
	"""Creates a trade using an XML <text> tag with a int placeholder for iData."""
	return addTrade(type, SimpleTradeFormat(name, type, key))

def addAppendingTrade(name, type, key, argsFunction, text="%s"):
	"""Creates a trade using an XML <text> tag with a single appended string placeholder."""
	format = addTrade(type, AppendingTradeFormat(name, type, key, text))
	if argsFunction is not None:
		format.getParameters = lambda player, trade: argsFunction(player, trade)
	return format

def addComplexTrade(name, type, argsFunction, textFunction=None):
	"""Creates a trade using an XML <text> tag with any number of placeholders."""
	format = addTrade(type, ComplexTradeFormat(name, type))
	if argsFunction is not None:
		format.getParameters = lambda player, trade: argsFunction(player, trade)
	if textFunction is not None:
		format.getText = lambda player, trade: textFunction(player, trade)
	return format

def addTrade(type, format):
	TRADE_FORMATS[type] = format
	return format


## Functions for use as argsFunction: converting TradeData.iData into
## whatever you want to display in the formatted string.

def getTradeTech(player, trade):
	return gc.getTechInfo(trade.iData).getDescription()

def getTradeBonus(player, trade):
	return gc.getBonusInfo(trade.iData).getDescription()

def getTradeCity(player, trade):
	return PlayerUtil.getPlayer(player).getCity(trade.iData).getName()

def getTradeCivic(player, trade):
	return gc.getCivicInfo(trade.iData).getDescription()

def getTradeReligion(player, trade):
	return gc.getReligionInfo(trade.iData).getDescription()

def getTradePlayer(player, trade):
	return PlayerUtil.getPlayer(trade.iData).getName()

def getTradePeaceDeal(player, trade):
	BugUtil.debug("TradeUtil - peace treaty has iData %d", trade.iData)
	return BugUtil.getText("TXT_KEY_TRADE_PEACE_TREATY_STRING", (gc.getDefineINT("PEACE_TREATY_LENGTH"),))

#Afforess
def getTradeUnits(player, trade):
	return PlayerUtil.getPlayer(player).getUnit(trade.iData).getName()
#Afforess End
## Classes for Formatting TradeData

class BaseTradeFormat(object):
	def __init__(self, name, type):
		self.name = name
		self.type = type
	def format(self, player, trade):
		pass

class PlainTradeFormat(BaseTradeFormat):
	def __init__(self, name, type, key):
		super(PlainTradeFormat, self).__init__(name, type)
		self.key = key
	def format(self, player, trade):
		return BugUtil.getPlainText(self.key)

class SimpleTradeFormat(BaseTradeFormat):
	def __init__(self, name, type, key):
		super(SimpleTradeFormat, self).__init__(name, type)
		self.key = key
	def format(self, player, trade):
		return BugUtil.getText(self.key, (self.getParameters(player, trade),))
	def getParameters(self, player, trade):
		return trade.iData

class AppendingTradeFormat(BaseTradeFormat):
	def __init__(self, name, type, key, text="%s"):
		super(AppendingTradeFormat, self).__init__(name, type)
		self.key = key
		self.text = text
	def format(self, player, trade):
		return self.getText(player, trade) % (self.getParameters(player, trade),)
	def getText(self, player, trade):
		return BugUtil.getPlainText(self.key) + self.text
	def getParameters(self, player, trade):
		return trade.iData

class ComplexTradeFormat(BaseTradeFormat):
	def __init__(self, name, type):
		super(ComplexTradeFormat, self).__init__(name, type)
	def format(self, player, trade):
		return self.getText(player, trade) % (self.getParameters(player, trade),)
	def getText(self, player, trade):
		return "%s"
	def getParameters(self, player, trade):
		return trade.iData


## Initialization

def init():
	"""
	Performs one-time initialization after the game starts up.
	"""
	initCorporationBonuses()
	initFractionalTrade()
	initTradeableItems()


## Testing

def makeTrade(type, value=-1):
	trade = TradeData()
	trade.ItemType = TradeableItems(type)
	if value != -1:
		trade.iData = value
	return trade

def test(player, type, value):
	print format(player, makeTrade(type, value))

def testAll():
	for i in TRADE_FORMATS.keys():
		test(2, i, 1)

def testList():
	print format(2, [
		makeTrade(TradeableItems.TRADE_GOLD, 53),
		makeTrade(TradeableItems.TRADE_MAPS),
		makeTrade(TradeableItems.TRADE_PEACE, 1),
		makeTrade(TradeableItems.TRADE_CITY, 1),
		makeTrade(TradeableItems.TRADE_GOLD_PER_TURN, 6),
	])

STATUS_TRADE_ITEMS = (
	(TradeableItems.TRADE_MAPS, "Map"),
	(TradeableItems.TRADE_VASSAL, "Vassal"),
	(TradeableItems.TRADE_SURRENDER, "Surrender"),
	(TradeableItems.TRADE_OPEN_BORDERS, "Borders"),
	(TradeableItems.TRADE_DEFENSIVE_PACT, "Pact"),
	(TradeableItems.TRADE_PERMANENT_ALLIANCE, "Alliance"),
	(TradeableItems.TRADE_PEACE_TREATY, "Peace"),
#RevolutionDCM start - extra civlerts
	(TradeableItems.TRADE_WAR, "War"),
#RevolutionDCM end
	
)
DENIALS = {
	DenialTypes.NO_DENIAL : "None",
	DenialTypes.DENIAL_UNKNOWN : "Unknown",
	DenialTypes.DENIAL_NEVER : "Never",
	DenialTypes.DENIAL_TOO_MUCH : "Too Much",
	DenialTypes.DENIAL_MYSTERY : "Mystery",
	DenialTypes.DENIAL_JOKING : "Joking",
	DenialTypes.DENIAL_ANGER_CIVIC : "Anger Civic",
	DenialTypes.DENIAL_FAVORITE_CIVIC : "Favorite Civic",
	DenialTypes.DENIAL_MINORITY_RELIGION : "Minority Religion",
	DenialTypes.DENIAL_CONTACT_THEM : "Contact Them",
	DenialTypes.DENIAL_VICTORY : "Victory",
	DenialTypes.DENIAL_ATTITUDE : "Attitude",
	DenialTypes.DENIAL_ATTITUDE_THEM : "Attitude Them",
	DenialTypes.DENIAL_TECH_WHORE : "Tech Whore",
	DenialTypes.DENIAL_TECH_MONOPOLY : "Tech Monopoly",
	DenialTypes.DENIAL_POWER_US : "Power Us",
	DenialTypes.DENIAL_POWER_YOU : "Power You",
	DenialTypes.DENIAL_POWER_THEM : "Power Them",
	DenialTypes.DENIAL_TOO_MANY_WARS : "WHEOOH",
	DenialTypes.DENIAL_NO_GAIN : "No Gain",
	DenialTypes.DENIAL_NOT_ALLIED : "Not Allied",
	DenialTypes.DENIAL_RECENT_CANCEL : "Recent Cancel",
	DenialTypes.DENIAL_WORST_ENEMY : "Worst Enemy",
	DenialTypes.DENIAL_POWER_YOUR_ENEMIES : "Power Your Enemies",
	DenialTypes.DENIAL_TOO_FAR : "Too Far",
	# these aren't available during startup (dunno about later)
	#DenialTypes.DENIAL_VASSAL : "Vassal",
	#DenialTypes.DENIAL_WAR_NOT_POSSIBLE_US : "War Not Possible Us",
	#DenialTypes.DENIAL_WAR_NOT_POSSIBLE_THEM : "War Not Possible Them",
	#DenialTypes.DENIAL_PEACE_NOT_POSSIBLE_US : "Peace Not Possible Us",
	#DenialTypes.DENIAL_PEACE_NOT_POSSIBLE_THEM : "Peace Not Possible Them",
}

def printStatus(ePlayer, eAskingPlayer=None):
	player = PlayerUtil.getPlayer(ePlayer)
	if eAskingPlayer is None:
		eAskingPlayer = PlayerUtil.getActivePlayerID()
	print "Trade Status -- %s" % player.getName()
	for eItem, name in STATUS_TRADE_ITEMS:
		tradeData = TradeData()
		tradeData.ItemType = eItem
		can = player.canTradeItem(eAskingPlayer, tradeData, False)
		denial = player.getTradeDenial(eAskingPlayer, tradeData)
		will = denial == DenialTypes.NO_DENIAL
		if denial in DENIALS:
			denial = DENIALS[denial]
		else:
			denial = str(denial)
		if not can:
			if will:
				print "%s: can't but will" % (name)
			else:
				print "%s: can't and won't because %s" % (name, denial)
		else:
			if will:
				print "%s: will" % (name)
			else:
				print "%s: won't because %s" % (name, denial)
