## ReligionUtil
##
## Collection of utility functions for dealing with religions.
## They are designed to be generic and work with new religions 
## and even new types of religious buildings and units.
##
## Religions
##
##   getNumReligions()
##     Returns the number of religions.
##
##   isValid(iReligion)
##     Returns True if <iReligion> is a valid religion (not None or NO_RELIGION).
##
##   getAllReligions()
##     Returns a tuple containing all valid religion IDs (0, 1, ..., NUM_RELIGIONS-1).
##
##   getFoundedReligions()
##     Returns a list of religion IDs that have been founded.
##
## Buildings
##
##   getNumBuildingTypes()
##     Returns the number of religious building types.
##     This is normally 4 for Temple, Cathedral, Monastery, Shrine.
##
##   getBuildingTypes():
##     Returns the BuildingTypes in order.
##
##   getBuildingType(index)
##     Returns the BuildingType for <index> holding its description and icon.
##
##   getBuildings(iReligion)
##     Returns the list of building info types for <iReligion>.
##
##   getBuilding(iReligion, index)
##     Returns the single <index>th building info type for <iReligion>.
##
##   BUILDING_TEMPLE, BUILDING_CATHEDRAL, BUILDING_MONASTERY, BUILDING_SHRINE
##     Each building type defined gets a module-level constant.
##     The description is added to "BUILDING_" after making it uppercase and replacing spaces with underscores (_)
##
## Units
##
##   getNumUnitTypes()
##     Returns the number of religious unit types.
##     This is normally 1 for Missionary.
##
##   getUnitTypes():
##     Returns the UnitTypes in order.
##
##   getUnitType(index)
##     Returns the UnitType for <index> holding its description and icon.
##
##   getUnits(iReligion)
##     Returns the list of unit info types for <iReligion>.
##
##   getUnit(iReligion, index)
##     Returns the single <index>th unit info type for <iReligion>.
##
##   UNIT_MISSIONARY
##     Each unit type defined gets a module-level constant.
##     The description is added to "UNIT_" after making it uppercase and replacing spaces with underscores (_)
##
## Cities
##
##   getCityReligions(city)
##     Returns a list of religion IDs that are present in <city>.
##
##   getCityHolyReligions(city)
##     Returns a list of religion IDs for which <city> is the holy city.
##
## Players
##
##   getPlayerReligions(player)
##     Returns a list of religion IDs that are present in at least one of <player>'s cities.
##
##   getPlayerHolyReligions(player)
##     Returns a list of religion IDs for which <player> owns the holy city.
##
## MODDERS
##
##   - Nothing needs to be done here when adding new religions
##   - If you define new types of religious buildings, you must add a BuildingType() call to init()
##     and write a tester function.
##   - If you define new types of religious units, you must add a UnitType() call to init()
##     and write a tester function.
##
## Notes
##   - Must be initialized externally by calling init()
##
## Copyright (c) 2009 The BUG Mod.
##
## Author: EmperorFool

from CvPythonExtensions import *

import BugUtil
import FontUtil


## Globals

gc = CyGlobalContext()

NUM_RELIGIONS = -1
ALL_RELIGIONS = None

NUM_BUILDING_TYPES = 0
BUILDINGS = []
BUILDINGS_BY_RELIGION = []

NUM_UNIT_TYPES = 0
UNITS = []
UNITS_BY_RELIGION = []

# these constants are defined automatically during initialization
# they are here only to make Eclipse happy
BUILDING_TEMPLE = -1
BUILDING_CATHEDRAL = -1
BUILDING_MONASTERY = -1
BUILDING_SHRINE = -1
UNIT_MISSIONARY = -1


## Religions

def getNumReligions():
    """
    Returns the number of religions.
    """
    return NUM_RELIGIONS

def isValid(iReligion):
    """
    Returns True if <iReligion> is a valid religion (not None or NO_RELIGION).
    """
    return iReligion > -1 and iReligion < NUM_RELIGIONS

def getAllReligions():
    """
    Returns a tuple containing all valid religion IDs (0, 1, ..., NUM_RELIGIONS-1).
    """
    return ALL_RELIGIONS

def getFoundedReligions():
    """
    Returns a list of religion IDs that have been founded.
    """
    religions = []
    game = gc.getGame()
    for iReligion in ALL_RELIGIONS:
        if game.getReligionGameTurnFounded(iReligion) >= 0:
            religions.append(iReligion)
    return religions


## Buildings

def getNumBuildingTypes():
    """
    Returns the number of religious building types.
    """
    return NUM_BUILDING_TYPES

def getBuildingTypes():
    """
    Returns the BuildingTypes in order.
    """
    return BUILDINGS

def getBuildingType(index):
    """
    Returns the <index>th BuildingType.
    """
    try:
        return BUILDINGS[index]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid building type %i", index)
        return None

def getBuildings(iReligion):
    """
    Returns the list of building info types for <iReligion>.
    """
    try:
        return BUILDINGS_BY_RELIGION[iReligion]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid religion %i", iReligion)
        return [-1] * NUM_BUILDING_TYPES

def getBuilding(iReligion, index):
    """
    Returns the single <index>th building info type for <iReligion>.
    """
    try:
        return BUILDINGS_BY_RELIGION[iReligion][index]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid religion %i or building type %i", iReligion, index)
        return -1


def getBuildingReligion(info):
    """
    Returns the religion <info> is tied to.
    
    All four building types have iReligionType set to the religion, 
    but the Shrine doesn't have the religion as a prereq.
    No other buildings have iReligionType, so this should be safe.
    """
    return info.getReligionType()

def isTemple(info, iReligion):
    """
    Returns True if <info> is the Temple for <iReligion>.
    """
    return info.getHappiness() > 0

def isCathedral(info, iReligion):
    """
    Returns True if <info> is the Cathedral for <iReligion>.
    """
    return info.getCommerceModifier(CommerceTypes.COMMERCE_CULTURE) > 0

def isMonastery(info, iReligion):
    """
    Returns True if <info> is the Monastery for <iReligion>.
    """
    return info.getCommerceModifier(CommerceTypes.COMMERCE_RESEARCH) > 0

def isShrine(info, iReligion):
    """
    Returns True if <info> is the Shrine for <iReligion>.
    """
    return info.getGlobalReligionCommerce() != -1

# MOD: Add a tester function for each new religious building type you define
#      You tie these to a description and icon in init()

#def isInquisitionOffice(info, iReligion):
#   """
#   Returns True if <info> is the Inquisition Office for <iReligion>.
#   """
#   return info.isInquisitionOffice()


## Units

def getNumUnitTypes():
    """
    Returns the number of religious unit types.
    """
    return NUM_UNIT_TYPES

def getUnitTypes():
    """
    Returns the UnitTypes in order.
    """
    return UNITS

def getUnitType(index):
    """
    Returns the <index>th UnitType.
    """
    try:
        return UNITS[index]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid unit type %i", index)
        return None

def getUnits(iReligion):
    """
    Returns the list of unit info types for <iReligion>.
    """
    try:
        return UNITS_BY_RELIGION[iReligion]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid religion %i", iReligion)
        return [-1] * NUM_UNIT_TYPES

def getUnit(iReligion, index):
    """
    Returns the single <index>th unit info type for <iReligion>.
    """
    try:
        return UNITS_BY_RELIGION[iReligion][index]
    except IndexError:
        BugUtil.error("ReligionUtil - invalid religion %i or unit type %i", iReligion, index)
        return -1


def getUnitReligion(info):
    """
    Returns the religion <info> is tied to.
    
    <ReligionType> is not set for any units, but luckily only missionaries have <PrereqReligion>.
    """
    return info.getPrereqReligion()

def isMissionary(info, iReligion):
    """
    Returns True if <info> is the Missionary for <iReligion>.
    """
    return info.getReligionSpreads(iReligion)

# MOD: Add a tester function for each new religious unit type you define
#      You tie these to a description and icon in init()

#def isInquisitor(info, iReligion):
#   """
#   Returns True if <info> is the Inquisitor for <iReligion>.
#   """
#   return info.getReligionRemoves(iReligion)


## Cities

def getCityReligions(city):
    """
    Returns a list of religion IDs that are present in <city>.
    """
    religions = []
    for eReligion in range(NUM_RELIGIONS):
        if city.isHasReligion(eReligion):
            religions.append(eReligion)
    return religions

def getCityHolyReligions(city):
    """
    Returns a list of religion IDs for which <city> is the holy city.
    """
    religions = []
    for eReligion in range(NUM_RELIGIONS):
        if city.isHolyCityByType(eReligion):
            religions.append(eReligion)
    return religions


## Players

def getPlayerReligions(player):
    """
    Returns a list of religion IDs that are present in at least one of <player>'s cities.
    """
    religions = []
    for eReligion in range(NUM_RELIGIONS):
        if player.getHasReligionCount(eReligion) > 0:
            religions.append(eReligion)
    return religions

def getPlayerHolyReligions(player):
    """
    Returns a list of religion IDs for which <player> owns the holy city.
    """
    religions = []
    for eReligion in range(NUM_RELIGIONS):
        if player.hasHolyCity(eReligion):
            religions.append(eReligion)
    return religions


## Initialization

def init():
    BuildingType("Temple", FontUtil.getChar("happy"), isTemple)
    BuildingType("Cathedral", FontUtil.getChar("commerce culture"), isCathedral)
    BuildingType("Monastery", FontUtil.getChar("commerce research"), isMonastery)
    BuildingType("Shrine", FontUtil.getChar("commerce gold"), isShrine)
# MOD: Add a BuildingType() call for each new religious building type you define
#   BuildingType("Inquisition Office", FontUtil.getChar("cancel"), isInquisitionOffice)
    
    UnitType("Missionary", FontUtil.getChar("religion"), isMissionary)
# MOD: Add a BuildingType() call for each new religious unit type you define
#   UnitType("Inquisitor", FontUtil.getChar("religion"), isInquisitor)
    
    global NUM_RELIGIONS, ALL_RELIGIONS
    NUM_RELIGIONS = gc.getNumReligionInfos()
    ALL_RELIGIONS = range(NUM_RELIGIONS)
    for iReligion in ALL_RELIGIONS:
        BUILDINGS_BY_RELIGION.append([-1] * NUM_BUILDING_TYPES)
        UNITS_BY_RELIGION.append([-1] * NUM_UNIT_TYPES)
    
    for iBldg in range(gc.getNumBuildingInfos()):
        bldg = gc.getBuildingInfo(iBldg)
        iReligion = getBuildingReligion(bldg)
        if iReligion >= 0:
            for type in BUILDINGS:
                if type.matches(bldg, iReligion):
                    BUILDINGS_BY_RELIGION[iReligion][type.index] = iBldg
                    BugUtil.debug("ReligionUtil.init - %s %s is %i: %s", 
                                gc.getReligionInfo(iReligion).getDescription(), type.description, iBldg, bldg.getDescription())
                    break
    
    for iUnit in range(gc.getNumUnitInfos()):
        unit = gc.getUnitInfo(iUnit)
        iReligion = getUnitReligion(unit)
        if iReligion >= 0:
            for type in UNITS:
                if type.matches(unit, iReligion):
                    UNITS_BY_RELIGION[iReligion][type.index] = iUnit
                    BugUtil.debug("ReligionUtil.init - %s %s is %i: %s", 
                                gc.getReligionInfo(iReligion).getDescription(), type.description, iUnit, unit.getDescription())
                    break


## Building and Unit Types

class Type:
    def __init__(self, type, description, icon, testFunc):
        counter = "NUM_%s_TYPES" % type
        key = type + "_" + description.upper().replace(" ", "_")
        g = globals()
        index = g[counter]
        g[counter] += 1
        g[key] = index
        g[type + "S"].append(self)
        self.index = index
        self.key = key
        self.description = description
        self.icon = icon
        self.testFunc = testFunc
    def matches(self, info, iReligion):
        """
        Returns True if <info> matches the type of this object for <iReligion> by calling the test function.
        """
        return self.testFunc(info, iReligion)

class BuildingType(Type):
    def __init__(self, description, icon, testFunc):
        """
        Defines a new religious building type.
        
        description - display string such as "Temple" (may contain spaces)
        icon - string holding the icon (font glyph)
        testFunc - function that returns True if the given CvBuildingInfo matches the type
        """
        Type.__init__(self, "BUILDING", description, icon, testFunc)

class UnitType(Type):
    def __init__(self, description, icon, testFunc):
        """
        Defines a new religious unit type.
        
        description - display string such as "Missionary" (may contain spaces)
        icon - string holding the icon (font glyph)
        testFunc - function that returns True if the given CvUnitInfo matches the type
        """
        Type.__init__(self, "UNIT", description, icon, testFunc)
