###############################################################################
# File:        guObjects.py
# Appointment: Game objects
# Project:     GalaxyU
# Author:      AR
# History:
# 26.11.2018 CDataItem added to support additional data
# 11.10.2013 Propagationg ships FleetName and Efficiency to reports
# 10.10.2013 Added bombing support
# 06.12.2012 Created
###############################################################################
import guReport
import zx
import pgTools
import guApp
import guShipCalc
import guShipsView
import guBattle
import guShipLayout
import guGBView
import guProdOrdersListView
import guSpaceGroupsListView

import wx
import pygame
import math
import ctypes
from ctypes import *

# Global const for 'Not Available'
NA = "N/A"

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Calculates size factor used to show object on a map
def CalcSizeFactor(aSize, powK):
    return math.pow(aSize, 1.0/powK)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Space object
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CSpaceObject:
    __scaleFactor = CalcSizeFactor(1, 5)

    #---------------------------------------------------------------------------
    def __init__(self, aId, aX = 0, aY = 0):
        self.Id = int(aId)
        
        # Original server coordinates
        self.OriginalX = aX
        self.OriginalY = aY
        
        # Client normilized coordinates
        self.X = float(aX)*guReport.COORDINATES_MULT
        self.Y = float(aY)*guReport.COORDINATES_MULT
        

    #---------------------------------------------------------------------------
    # Virtual: calculates diameter on the screen. aCalcSizeFunc(aSize, aMinSize)
    def CalcOnScreenDiameter(self, aCalcSizeFunc):
        return aCalcSizeFunc(self.__scaleFactor, 1)
        
    #---------------------------------------------------------------------------
    # Virtual: returns a description string
    def GetDescription(self):
        return ""
        
    #---------------------------------------------------------------------------
    # Virtual: returns a short description
    def GetShortDesc(self):
        return ""

    #---------------------------------------------------------------------------
    # Returns 'id' name
    def GetIdName(self):
        return "#" + str(self.Id)
        
    #---------------------------------------------------------------------------
    # Returns location string (x,y)
    def GetLocationStr(self):
        return "(" + str(self.OriginalX) + "," + str(self.OriginalY) + ")"
        
    #---------------------------------------------------------------------------
    # Returns a menu object to perform some actions
    def GetPopupMenu(self, aParent):
        return None
        
    
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Space group
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CSpaceGroup(CSpaceObject):
    # StaTic images collection {colour : image}
    __IMAGES = {}

    __scaleFactor = CalcSizeFactor(0.1, 3)

    #---------------------------------------------------------------------------
    def __init__(self, aId = 0,
                       aX = 0, 
                       aY = 0, 
                       aMass = 0, 
                       aDestination = 0, 
                       aIsMyGroup = False,
                       aETA = 0,
                       aSpeed = 0):
        CSpaceObject.__init__(self, aId, aX, aY)

        self.Mass = float(aMass)
        self.Destination = int(aDestination)
        self.__destinationStar = None # lazy evaluated destination star
        self.IsMyGroup = aIsMyGroup
        self.ETA = int(aETA)
        self.Speed = float(aSpeed)
        self.__ships = None

    #---------------------------------------------------------------------------
    # Returns a destination star or None, if there is no destination
    def GetDestinationStar(self):
        if self.__destinationStar is None and self.Destination != 0:
            self.__destinationStar = guApp.Application.GetReport().FindStar(self.Destination)
        return self.__destinationStar

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def CalcOnScreenDiameter(self, aCalcSizeFunc):
        return aCalcSizeFunc(self.__scaleFactor, 2)*2

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def GetDescription(self):
        if self.Mass == 0:
            return "Unidentified ship group " + self.GetIdName() + " " + self.GetLocationStr()
        
        # Find destination
        star = guApp.Application.GetReport().FindStar(self.Destination)
        return "Ship group " + self.GetIdName() + " " + self.GetLocationStr() + \
               " to " + \
               ("<destination unknown>" if star is None else star.GetFullName()) + \
               ", W=" + "{0:.2f}".format(self.Mass) + \
               ", Speed=" + "{0:.2f}".format(self.Speed) + \
               ", ETA=" + str(self.ETA)
        
    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def GetShortDesc(self):
        s = ("Undef SG " if self.Mass == 0 else "SG ") + self.GetIdName()
        return s if self.Mass == 0 else s + "(W=" + str(self.Mass) + ")"

    #---------------------------------------------------------------------------
    # Returns group image
    def CreateImage(self, aColour):
        if aColour in self.__IMAGES:
            return self.__IMAGES[aColour]
            
        sz = 50
        image = pygame.Surface((sz, sz))
        image.fill((0,0,0))
        image.set_colorkey((0,0,0)) # make image transparent

        # Calc center
        r = int(sz/2)
        cvm = pgTools.CColourVolMaker(aColour, r, 0.1)
        x = y = r
        for i in range(0, r):
            pygame.draw.circle(image, cvm.GetNextColour(), (int(x), int(y)), r - i)
        
        self.__IMAGES[aColour] = image;
        return self.__IMAGES[aColour]


    #---------------------------------------------------------------------------
    def ShowShips(self, aParent):
        if len(self.GetShips()) > 0:
            guShipsView.ShipsList(\
                lambda: self.GetShips(), "Ships in the group #" + str(self.Id))

    #---------------------------------------------------------------------------
    # Returns ships for the group
    def GetShips(self):
        if self.__ships is None:
            self.__ships =\
                [] if not self.IsMyGroup else\
                    zx.GetSubListFor(\
                        guApp.Application.GetReport().Ships,
                        lambda ship: ship.X == self.X and ship.Y == self.Y)
        return self.__ships

    #---------------------------------------------------------------------------
    # Adds to menu an item to show ships in the group
    # Returns True if item has been added
    def AddShowShipsMenuItem(self, aParent, aMenu):
        if len(self.GetShips()) > 0:
            mi = wx.MenuItem(aMenu, wx.NewId(), 'Show ships in the group')
            aParent.Bind(wx.EVT_MENU, lambda event: self.ShowShips(aParent), mi)
            aMenu.AppendItem(mi)
            return True
        return False
    
    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    # Returns a menu object to perform some actions
    def GetPopupMenu(self, aParent):
        menu = wx.Menu()
        return menu if self.AddShowShipsMenuItem(aParent, menu) else None
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Ship component
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CShipComponent:
    # Component types
    SHIP_COMPONENT_UNKNOWN     = 0
    SHIP_COMPONENT_DRIVE       = 1 # drive
    SHIP_COMPONENT_SHIELD      = 2 # shield generator
    SHIP_COMPONENT_CARGO       = 3 # cargo 
    SHIP_COMPONENT_LASER       = 4 # laser cannon
    SHIP_COMPONENT_MISSILE     = 5 # missile launcher
    SHIP_COMPONENT_REPAIR_UNIT = 6 # repair unit
    SHIP_COMPONENT_COLONY_UNIT = 7 # colony unit

    # Components Char type codes
    CHAR_CODES = ['U', 'D', 'S', 'C', 'L', 'M', 'R', 'K']
    
    # Weapon target types
    TARGET_TYPE_ANTI_SHIP = 0
    TARGET_TYPE_ANTI_MISSILE = 1
    
    WHP_Default = 0.3 # default WH percentage for missiles

    #---------------------------------------------------------------------------
    def __init__(self, aId = 0,
                       aType = SHIP_COMPONENT_UNKNOWN,
                       aMass = 0,
                       aQuantity = 0,
                       aDestroyedMass = 0,
                       aTech = 1,
                       aTargetType = TARGET_TYPE_ANTI_SHIP,
                       aCargo = "",
                       aMaxLaunchCount = 0,
                       aWarHeadePercentage = WHP_Default,
                       aEfficiency = 1,
                       aShotPower = 0):
        self.Id = int(aId)
        self.Type = int(aType)
        self.Mass = float(aMass)
        self.Quantity = int(aQuantity)
        self.DestroyedMass = float(aDestroyedMass)
        self.Efficiency = float(aEfficiency)
        self.Tech = float(aTech)
        self.TargetType = int(aTargetType)
        self.Cargo = aCargo # string like "COL:15.0;MAT:10"
        self.MaxLaunchCount = int(aMaxLaunchCount)
        self.WarHeadePercentage = float(aWarHeadePercentage)
        self.ShotPower = float(aShotPower)

    #---------------------------------------------------------------------------
    def __cmp__(self, other):
        return 0 if self.Id == other.Id and \
               self.Type == other.Type and \
               self.Mass == other.Mass and \
               self.Quantity == other.Quantity and \
               self.DestroyedMass == other.DestroyedMass and \
               self.Efficiency == other.Efficiency and \
               self.Tech == other.Tech and \
               self.TargetType == other.TargetType and \
               self.Cargo == other.Cargo and \
               self.MaxLaunchCount == other.MaxLaunchCount and \
               self.WarHeadePercentage == other.WarHeadePercentage else 1
    
    #---------------------------------------------------------------------------
    # Returns component's mass
    def CalcMass(self):
        return guApp.Application.API.guCalcComponentMass(self.GetCommandString())
        
    #---------------------------------------------------------------------------
    # Returns true if component is a single
    def IsSingle(self):
        return guApp.Application.API.guIsComponentTypeSingle(self.Type)
        
    #---------------------------------------------------------------------------
    # Returns type name
    def GetTypeName(self):
        return guApp.Application.API.guGetShipComponentTypeName(self.Type)  

    #---------------------------------------------------------------------------
    # Returns a string compartible with server command processor to create a component
    def GetCommandString(self):
        res = self.CHAR_CODES[self.Type] + str(self.Mass)
        # Special processing for weapon types
        if self.Type == self.SHIP_COMPONENT_LASER or self.Type == self.SHIP_COMPONENT_MISSILE:
            res += "x" + str(self.Quantity) + \
                   "@" + ("am" if self.TargetType == self.TARGET_TYPE_ANTI_MISSILE else "as") + \
                   (("/" + str(1 if self.MaxLaunchCount == 0 else self.MaxLaunchCount) + "/0/" + \
                           str(float(self.WarHeadePercentage))) if self.Type == self.SHIP_COMPONENT_MISSILE else "")
        return res


    #---------------------------------------------------------------------------
    # Returns default shot power for the component or 0 in the error case
    def CalcDefaultShotPower(self):
        components = guApp.Application.GetReport().LoadShipComponentsByCB(\
            lambda cb: guApp.Application.API.guGetShipTypeComponentsFromCmd(self.GetCommandString(), cb))
        if len(components) == 0:
            guApp.Application.Log(\
                zxLog.LOG_ERROR,
                'Invalid component code ' + self.GetCommandString() + '')
            return 0
        else:
            return components[0].ShotPower


    #---------------------------------------------------------------------------
    # Sets default shot power
    def SetDefaultShotPower(self):
        self.ShotPower = self.CalcDefaultShotPower()

        
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Ship type
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CShipType:
    #---------------------------------------------------------------------------
    # Returns a string compartible with server command processor to create a set of components
    @staticmethod
    def GetComponentsCommandString(aComponents):
        res = ""
        for c in aComponents:
            res += c.GetCommandString() + ","
        return res

    #---------------------------------------------------------------------------
    # Returns a components list by type
    @staticmethod
    def FindComponents(aType, aComponents):
        res = []
        for c in aComponents:
            if c.Type == aType:
                res.append(c)
        return res

    #---------------------------------------------------------------------------
    # Returns a ship type key
    @staticmethod
    def GetTypeKey(aNationName, aTypeName):
        return aNationName + "@" + aTypeName

    #---------------------------------------------------------------------------
    # Returns image
    @staticmethod
    def CreateImage(aColour, aLayout):
        szX = CShip.SHIP_IMAGE_SIZE
        szY = CShip.SHIP_IMAGE_SIZE
        image = pygame.Surface((szX, szY))
        image.fill((0,0,0))
        image.set_colorkey((0,0,0)) # make image transparent
        aLayout.Draw(image, aColour)
        return image
            

    #---------------------------------------------------------------------------
    def __init__(self, aName = "", aNationName = "", aShipClass = 0):
        self.Name = aName
        self.NationName = aNationName
        self.ShipClass = int(aShipClass)
        self.Components = []
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Ship
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CShip(CSpaceObject):
    # Ship image size
    SHIP_IMAGE_SIZE = 200

    #---------------------------------------------------------------------------
    def __init__(self, aId = 0,
                       aNationName = "",
                       aTypeName = "",
                       aName = "",
                       aBuildYear = 0,
                       aTravelledDistance = 0,
                       aBatlleCounter = 0,
                       aDamagedEnemyShipsCounter = 0,
                       aDestroyedEnemyShipsCounter = 0,
                       aDestroyedEnemyShipsMass = 0,
                       aDamagedCounter = 0,
                       aCombatFactor = 0,
                       aX = 0,
                       aY = 0,
                       aInSpace = False,
                       aFleetName = "",
                       aDefaultFleetName = "",
                       aEfficiency = 0,
                       aLoadedMass = 0,
                       aAssignment = "",
                       aUpgradeStatus = ""):
        CSpaceObject.__init__(self, aId, aX, aY)
        self.NationName = aNationName
        self.TypeName = aTypeName
        self.Name = aName
        self.BuildYear = int(aBuildYear)
        self.TravelledDistance = float(aTravelledDistance)
        self.BatlleCounter = int(aBatlleCounter)
        self.DamagedEnemyShipsCounter = int(aDamagedEnemyShipsCounter)
        self.DestroyedEnemyShipsCounter = int(aDestroyedEnemyShipsCounter)
        self.DestroyedEnemyShipsMass = float(aDestroyedEnemyShipsMass)
        self.DamagedCounter = int(aDamagedCounter)
        self.CombatFactor = float(aCombatFactor)
        self.InSpace = bool(aInSpace)
        self.FleetName = aFleetName
        self.DefaultFleetName = aDefaultFleetName
        self.Efficiency = float(aEfficiency)
        self.LoadedMass = float(aLoadedMass)
        self.Assignment = aAssignment
        self.UpgradeStatus = aUpgradeStatus
        self.Components = []
        self.Star = None # can be set to star during loading
        # Lazy evaluated internal fields
        self.Reset()

    def __cmp__(self, other):
        if len(self.Components) != len(other.Components):
            return 1
        for i in range(0, len(self.Components)):
            if self.Components[i] != other.Components[i]:
                return 1
        return 0 if self.Id == other.Id and \
               self.NationName == other.NationName and \
               self.TypeName == other.TypeName and \
               self.Name == other.Name and \
               self.BuildYear == other.BuildYear and \
               self.TravelledDistance == other.TravelledDistance and \
               self.BatlleCounter == other.BatlleCounter and \
               self.DamagedEnemyShipsCounter == other.DamagedEnemyShipsCounter and \
               self.DestroyedEnemyShipsCounter == other.DestroyedEnemyShipsCounter and \
               self.DestroyedEnemyShipsMass == other.DestroyedEnemyShipsMass and \
               self.DamagedCounter == other.DamagedCounter and \
               self.CombatFactor == other.CombatFactor and \
               self.InSpace == other.InSpace and \
               self.FleetName == other.FleetName and \
               self.DefaultFleetName == other.DefaultFleetName and \
               self.Efficiency == other.Efficiency and \
               self.LoadedMass == other.LoadedMass and \
               self.Assignment == other.Assignment else 1
   

    #---------------------------------------------------------------------------
    # Ship type calculations:
    # Mass
    def ShipTypeCalcMass(self):
        if self.__shipTypeMass is None:
            self.__shipTypeMass = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 0)
        return self.__shipTypeMass

    # Mass with full cargo
    def ShipTypeCalcMaxLoadedMass(self):
        if self.__shipTypeMaxLoadedMass is None:
            self.__shipTypeMaxLoadedMass = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 1)
        return self.__shipTypeMaxLoadedMass

    # Speed    
    def ShipTypeCalcSpeed(self):
        if self.__shipTypeSpeed is None:
            self.__shipTypeSpeed = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 2)
        return self.__shipTypeSpeed

    # Speed with full cargo
    def ShipTypeCalcMaxLoadedSpeed(self):
        if self.__shipTypeMaxLoadedSpeed is None:
            self.__shipTypeMaxLoadedSpeed = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 3)
        return self.__shipTypeMaxLoadedSpeed

    # Shield    
    def ShipTypeCalcShield(self):
        if self.__shipTypeShield is None:
            self.__shipTypeShield = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 4)
        return self.__shipTypeShield

    # Bombing    
    def ShipTypeCalcBombing(self):
        if self.__shipTypeBombing is None:
            self.__shipTypeBombing = \
                guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 6)
        return self.__shipTypeBombing
        
    # Laser max shot
    def ShipTypeCalcLaserMaxShot(self):
        return guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 7)

    # Missile max shot
    def ShipTypeCalcMissileMaxShot(self):
        return guApp.Application.API.guCalcShipTypeParam(\
                    self.NationName, self.GetComponentsCommandString(), 8)

    # Returns ship cargo bay capacity
    def ShipTypeCalcMaxCargoCapacity(self):
        return guApp.Application.API.guCalcShipTypeParam(\
            self.NationName, self.GetComponentsCommandString(), 9)

    # Returns ship loaded cargo
    def ShipTypeCalcLoadedCargo(self):
        return guApp.Application.API.guCalcShipTypeParam(\
            self.NationName, self.GetComponentsCommandString(), 10)

    # Returns ship free cargo space
    def ShipTypeCalcCargoFreeSpace(self):
        return guApp.Application.API.guCalcShipTypeParam(\
            self.NationName, self.GetComponentsCommandString(), 11)
            
    #---------------------------------------------------------------------------
    # Resets lazy evaluated values
    def Reset(self):
        self.__destination = None # lazy evaluated ship destination
        self.__destinationStar = None # lazy evaluated ship destination star
        self.__ETA = None # lazy evaluated ship ETA to destination
        self.__shipTypeMass = None
        self.__shipTypeMaxLoadedMass = None
        self.__shipTypeSpeed = None
        self.__shipTypeMaxLoadedSpeed = None
        self.__shipTypeShield = None
        self.__shipTypeBombing = None
    

    #---------------------------------------------------------------------------
    # Returns a ship type key
    def GetTypeKey(self):
        return CShipType.GetTypeKey(self.NationName, self.TypeName)
     
    #---------------------------------------------------------------------------
    # Returns a components list by type
    def FindComponents(self, aType):
        return CShipType.FindComponents(aType, self.Components)

    #---------------------------------------------------------------------------
    # Returns a string compartible with server command processor to create a set of components
    def GetComponentsCommandString(self):
        return CShipType.GetComponentsCommandString(self.Components)
        
    
    #---------------------------------------------------------------------------
    # Returns full ship name (name + id or just id)
    def GetFullShipName(self):
        name = '' if self.Name == '?' else self.Name
        if name != '':
            b1 = ' ('
            b2 = ')'
        else:
            b1 = ' '
            b2 = ''
        return name + b1 + '#' + str(self.Id) + b2

    #---------------------------------------------------------------------------
    # Returns a destination star id or 0, if there is no destination
    # The destination always exists for ships in space.
    # If ship not in space, but has destination - it means 'launched'
    def GetDestination(self):
        if self.__destination is None:
            self.__destination = int(guApp.Application.API.guGetShipParam(self.NationName, self.Id, 0))
        return self.__destination

    #---------------------------------------------------------------------------
    # Returns a destination star or None, if there is no destination
    def GetDestinationStar(self):
        if self.__destinationStar is None:
            if self.GetDestination() != 0:
                self.__destinationStar = guApp.Application.GetReport().FindStar(self.GetDestination())
            if self.__destinationStar is None:
                # Check, may be ship is joining a fleet?
                joiningFleet = \
                    guApp.Application.GetReport().GetJoiningFleetNameFromAssignment(self.Assignment)
                if len(joiningFleet) > 0:
                    # Make sure it's not a system fleet'
                    fleet = zx.Find(\
                        guApp.Application.GetReport().Fleets,
                        lambda f: f.Name == joiningFleet and not f.IsSystem)
                    if fleet is not None:
                        ship = zx.Find(\
                            guApp.Application.GetReport().Ships,
                            lambda s: s.FleetName == joiningFleet)
                        if ship is not None and not ship.InSpace:
                            self.__destinationStar = ship.Star

        return self.__destinationStar
        
    
    #---------------------------------------------------------------------------
    # Returns ship's ETA to current destination or 0
    def ETA(self):
        if self.__ETA is None:
            self.__ETA = int(guApp.Application.API.guGetShipParam(self.NationName, self.Id, 1))
        return self.__ETA

    #---------------------------------------------------------------------------
    # Returns ship speed
    def GetSpeed(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 2)

    #---------------------------------------------------------------------------
    # Returns ship speed with full cargo
    def GetMaxLoadedSpeed(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 4)

    #---------------------------------------------------------------------------
    # Returns ship shield
    def GetShield(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 3)

    #---------------------------------------------------------------------------
    # Returns ship bombing
    def GetBombing(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 5)

    #---------------------------------------------------------------------------
    # Returns ship laser max shot
    def GetLaserMaxShot(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 6)

    #---------------------------------------------------------------------------
    # Returns ship missile max shot
    def GetMissileMaxShot(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 7)

    #---------------------------------------------------------------------------
    # Returns ship cargo bay capacity
    def GetMaxCargoCapacity(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 8)

    #---------------------------------------------------------------------------
    # Returns ship loaded cargo
    def GetLoadedCargo(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 9)

    #---------------------------------------------------------------------------
    # Returns ship free cargo space
    def GetCargoFreeSpace(self):
        return guApp.Application.API.guGetShipParam(self.NationName, self.Id, 10)

    #---------------------------------------------------------------------------
    # Returns True if ship is launched
    def IsLaunched(self):
        return not self.InSpace and self.GetDestination() > 0
    
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Group of ships in the star system
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CShipGroup(CSpaceObject):

    #---------------------------------------------------------------------------
    def __init__(self, aId = 0,
                       aX = 0, 
                       aY = 0,
                       aIsFriend = False, 
                       aStarId = 0, # star id where the group is
                       aShips = [] # Ships belogning to the group
                       ):
        CSpaceObject.__init__(self, aId, aX, aY)

        self.StarId = int(aStarId)
        self.Ships = aShips
        self.IsFriend = aIsFriend
        self.SuppressLabel = False # used in map and planet view to suppress label
        self.__scaleFactor = CalcSizeFactor(self.Ships[0].ShipTypeCalcMass(), 4)

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def CalcOnScreenDiameter(self, aCalcSizeFunc):
        return aCalcSizeFunc(self.__scaleFactor, 2)

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def GetDescription(self):
        # Get combined cargo, carried by the group
        combinedCargo = {}
        shipsIds = "" # ids string
        for ship in self.Ships:
            shipsIds += ("," if len(shipsIds) != 0 else "") + "#" + str(ship.Id)
            components = ship.FindComponents(CShipComponent.SHIP_COMPONENT_CARGO)
            if len(components) > 0:
                # (It can be only one component cargo)
                strMap = zx.CSVToMap(components[0].Cargo, ";", ":")
                # Add to combined cargo
                for cargoName, cargoAmount in strMap.items():
                    if cargoName in combinedCargo:
                        combinedCargo[cargoName] = \
                            "{0:.2f}".format(float(combinedCargo[cargoName]) + float(cargoAmount))
                    else:
                        combinedCargo[cargoName] = cargoAmount
                        
        # Convert it to string
        cargo = ""                        
        if len(combinedCargo) > 0:
            for cargoName, cargoAmount in combinedCargo.items():
                cargo += (";" if len(cargo) > 0 else "") + cargoName + ":" + cargoAmount
        
        sh = self.Ships[0]
        return sh.NationName + "/" + sh.TypeName + \
            ((" (fleet: " + sh.FleetName + ")") if sh.FleetName != "" else "") + \
            ": mass=" + str(sh.ShipTypeCalcMass()) + \
            ", number=" + str(len(self.Ships)) + "(" + shipsIds + ")" +\
            ("" if len(cargo) == 0 else (", Total cargo: " + cargo)) + \
            ("" if sh.GetDestination() <= 0 else (", launched to " + \
                    guApp.Application.GetReport().GetStarName(\
                    sh.GetDestination()) + " (ETA=" + str(sh.ETA()) + ")")) + \
            ("" if sh.Assignment == "" else (" [" + sh.Assignment + "]"))

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    # Returns a menu object to perform some actions
    def GetPopupMenu(self, aParent):
        menu = wx.Menu()
        mi = wx.MenuItem(menu, wx.NewId(), 'Show ships in the group')
        aParent.Bind(wx.EVT_MENU, lambda event: self.__OnShowShips(event), mi)
        menu.AppendItem(mi)
        mi = wx.MenuItem(menu, wx.NewId(), 'Show ship type')
        aParent.Bind(wx.EVT_MENU, lambda event: self.__OnShowShipType(event), mi)
        menu.AppendItem(mi)

        sh = self.Ships[0]
        if sh.FleetName != "":
            guApp.Application.GetReport().AddShipsJoiningFleetMenuItem(\
                aParent, menu, sh.FleetName, sh.NationName)

        return menu
    

    #---------------------------------------------------------------------------
    # Returns True if the group is friendly to the report viewer
    def IsFriendly(self):
        return self.IsFriend or len(self.Ships) > 0 and guApp.Application.GetReport() == self.Ships[0].NationName
        

    #---------------------------------------------------------------------------
    # Menu handler - shows ships
    def __OnShowShips(self, event):
        # We can't use self.Ships directly, because on Refresh it will show 'old' set of ships
        # - a state before possible change
        # We need to find the group in possibly chanhed report. Group id may change and
        # group coordinates may change. We can only rely on a ship id inside the group
        # The ship id is persistent
        def GetShips():
            g = zx.Find(\
                guApp.Application.GetReport().GetShipGroups(),
                lambda g: zx.Find(\
                    g.Ships,
                    lambda sh: \
                        sh.Id == self.Ships[0].Id and
                        sh.NationName == self.Ships[0].NationName and
                        sh.TypeName == self.Ships[0].TypeName) is not None)
            return g.Ships if g is not None else self.Ships
        guShipsView.ShipsList(lambda: GetShips(), "Ships in the group #" + str(self.Id))
        event.Skip()

    #---------------------------------------------------------------------------
    # Menu handler - shows ship type
    def __OnShowShipType(self, event):
        guApp.Application.WindowsCollection.RegisterAndShow(\
            lambda: guShipCalc.CShipDesignerFrame(\
                guApp.Application.GetMainWindow(), self.Ships[0], False))
        event.Skip()
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Fleet
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CFleet:
    #---------------------------------------------------------------------------
    def __init__(self, aNationName = "",
                       aName = "",
                       aIsSystem = False,
                       aNumberOfShips = 0,
                       aTotalMass = 0,
                       aAssignment = "",
                       aSpeed = 0,
                       aStarName = ""):
        self.NationName = aNationName
        self.Name = aName
        self.IsSystem = aIsSystem
        self.NumberOfShips = int(aNumberOfShips)
        self.TotalMass = float(aTotalMass)
        self.Assignment = aAssignment
        self.Speed = float(aSpeed)
        self.StarName = aStarName
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Transport Order
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CTransportOrder:
    #---------------------------------------------------------------------------
    def __init__(self, aNationName = "",
                       aOrderId = 0,
                       aCargoType = "",
                       aStartStarId = 0,
                       aDestinationStarId = 0,
                       aOrderedAmount = 0,
                       aProducedAmount = 0,
                       aStatus = ""):
        self.NationName = aNationName
        self.OrderId = int(aOrderId)
        self.CargoType = aCargoType
        self.StartStarId = int(aStartStarId)
        self.DestinationStarId = int(aDestinationStarId)
        self.OrderedAmount = float(aOrderedAmount)
        self.ProducedAmount = float(aProducedAmount)
        self.Status = aStatus
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Production order
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CProdOrder:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName     = "",
                 aStarId         = 0,
                 aOrderId        = 0,
                 aOrderType      = "",
                 aPriority       = 0,
                 aOrderedAmount  = 0,
                 aProducedAmount = 0,
                 aStatus         = "",
                 aInfo           = ""):
        self.NationName     = aNationName
        self.StarId         = int(aStarId)
        self.OrderId        = int(aOrderId)
        self.OrderType      = aOrderType
        self.Priority       = int(aPriority)
        self.OrderedAmount  = float(aOrderedAmount)
        self.ProducedAmount = float(aProducedAmount)
        self.Status         = aStatus
        self.Info           = aInfo
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Nation rating data
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CRatingData:    
    RATING_NA                = -1 # rating not available
    
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNation = "",
                 aRatingIndex = 0,
                 aRating = 0,
                 aCreated = RATING_NA,
                 aPopulation = RATING_NA,
                 aIndustry = RATING_NA,
                 aShipsNumber = RATING_NA,
                 aShipsMass = RATING_NA,
                 aTechDrive = RATING_NA,
                 aTechWeapon = RATING_NA,
                 aTechShield = RATING_NA,
                 aTechCargo = RATING_NA,
                 aTech = RATING_NA,
                 aColoniesNumber = RATING_NA,
                 aColoniesTotalSize = RATING_NA,
                 aMoney = RATING_NA,
                 aDebt = RATING_NA,
                 aPopulationDelta = 0,
                 aIndustryDelta = 0,
                 aShipsNumberDelta = 0,
                 aShipsMassDelta = 0,
                 aTechDriveDelta = 0,
                 aTechWeaponDelta = 0,
                 aTechShieldDelta = 0,
                 aTechCargoDelta = 0,
                 aTechDelta = 0,
                 aColoniesNumberDelta = 0,
                 aColoniesTotalSizeDelta = 0,
                 aMoneyDelta = 0,
                 aDebtDelta = 0,
                 aRatingDelta = 0):
        self.Nation = aNation
        self.RatingIndex = int(aRatingIndex)
        self.Rating = float(aRating)
        self.Created = int(aCreated)
        self.Population = float(aPopulation)
        self.Industry = float(aIndustry)
        self.ShipsNumber = int(aShipsNumber)
        self.ShipsMass = float(aShipsMass)
        self.TechDrive = float(aTechDrive)
        self.TechWeapon = float(aTechWeapon)
        self.TechShield = float(aTechShield)
        self.TechCargo = float(aTechCargo)
        self.Tech = float(aTech)
        self.ColoniesNumber = int(aColoniesNumber)
        self.ColoniesTotalSize = int(aColoniesTotalSize)
        self.Money = float(aMoney)
        self.Debt = float(aDebt)
        self.PopulationDelta = float(aPopulationDelta)
        self.IndustryDelta = float(aIndustryDelta)
        self.ShipsNumberDelta = int(aShipsNumberDelta)
        self.ShipsMassDelta = float(aShipsMassDelta)
        self.TechDriveDelta = float(aTechDriveDelta)
        self.TechWeaponDelta = float(aTechWeaponDelta)
        self.TechShieldDelta = float(aTechShieldDelta)
        self.TechCargoDelta = float(aTechCargoDelta)
        self.TechDelta = float(aTechDelta)
        self.ColoniesNumberDelta = int(aColoniesNumberDelta)
        self.ColoniesTotalSizeDelta = int(aColoniesTotalSizeDelta)
        self.MoneyDelta = float(aMoneyDelta)
        self.DebtDelta = float(aDebtDelta)
        self.RatingDelta = float(aRatingDelta)
        # lazy evaluated ruler
        self.__ruler = None 
        
    
    #---------------------------------------------------------------------------
    # Helper function - returns a tech for given ship type    
    def GetTechForShipComponent(self, aShipComponentType):
        if aShipComponentType == CShipComponent.SHIP_COMPONENT_DRIVE or \
                aShipComponentType == CShipComponent.SHIP_COMPONENT_REPAIR_UNIT:
            return self.TechDrive
        elif aShipComponentType == CShipComponent.SHIP_COMPONENT_SHIELD:
            return self.TechShield
        elif aShipComponentType == CShipComponent.SHIP_COMPONENT_LASER or \
                aShipComponentType == CShipComponent.SHIP_COMPONENT_MISSILE:
            return self.TechWeapon
        elif aShipComponentType == CShipComponent.SHIP_COMPONENT_CARGO:
            return self.TechCargo
        return 1
    

    #---------------------------------------------------------------------------
    # Returns ruler for nation
    def GetRuler(self):
        if self.__ruler is None:
            self.__ruler = zx.Find(\
                guApp.Application.GetReport().Rulers, lambda r: r.NationName == self.Nation)
            if self.__ruler is None:
                # Not found - set default one
                self.__ruler = CRuler(self.Nation, NA, NA)
        return self.__ruler   
                
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Nation load/unload orders
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CLoadOrder:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aIsLoad = "",
                 aShipId = 0,
                 aCargoType = "",
                 aMass = 0
                 ):
        self.NationName = aNationName
        self.IsLoad = aIsLoad
        self.ShipId = int(aShipId)
        self.CargoType = aCargoType
        self.Mass = float(aMass)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Known Nation
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CKnownNation:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aKnownNationName = "",
                 aLastTimeContact = 0,
                 aTechDrive = 0,
                 aTechWeapon = 0,
                 aTechShield = 0,
                 aTechCargo = 0,
                 aLastTechUpdated = 0,
                 aPopulation = 0,
                 aIndustry = 0,
                 aLastPopulationUpdated = 0):
        self.NationName = aNationName
        self.KnownNationName = aKnownNationName
        self.LastTimeContact = int(aLastTimeContact)
        self.TechDrive = float(aTechDrive)
        self.TechWeapon = float(aTechWeapon)
        self.TechShield = float(aTechShield)
        self.TechCargo = float(aTechCargo)
        self.LastTechUpdated = int(aLastTechUpdated)
        self.Population = float(aPopulation)
        self.Industry = float(aIndustry)
        self.LastPopulationUpdated = int(aLastPopulationUpdated)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Report message
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CReportMessage:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName     = "",
                 aType           = "",
                 aText           = "",
                 aAdditionalData = ""):
        self.NationName     = aNationName
        self.Type           = aType
        self.Text           = aText
        self.AdditionalData = aAdditionalData
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Ruler
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CRuler:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aName = "",
                 aTitle = "",
                 aGalaxyRulerTitle = ""):
        self.NationName = aNationName
        self.Name = aName
        self.Title = aTitle
        self.GalaxyRulerTitle = aGalaxyRulerTitle
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Additional data item
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CDataItem:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aId = "",
                 aName = "",
                 aType = "",
                 aData = ""):
        self.NationName = aNationName
        self.Id = aId
        self.Name = aName
        self.Type = aType
        self.Data = ""
        #Note: deliberately not saving data
        
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Science
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CScience:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aName       = "",
                 aDrive      = 0,
                 aWeapon     = 0,
                 aShield     = 0,
                 aCargo      = 0):
        self.NationName = aNationName
        self.Name = aName
        self.Drive = float(aDrive)
        self.Weapon = float(aWeapon)
        self.Shield = float(aShield)
        self.Cargo = float(aCargo)
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Money transaction
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CMoneyTransaction:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aDelta      = 0,
                 aResult     = 0,
                 aText       = ""):
        self.NationName = aNationName
        self.Delta = float(aDelta)
        self.Result = float(aResult)
        self.Text = aText
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Automated manager transaction
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CAutoManager:
    #---------------------------------------------------------------------------
    def __init__(self, 
                 aNationName = "",
                 aName       = "",
                 aIsActive   = False,
                 aPriority   = 0,
                 aStarsInfo  = "",
                 aInfo       = ""):
        self.NationName = aNationName
        self.Name = aName
        self.IsActive = aIsActive
        self.Priority = int(aPriority)
        self.StarsInfo = aStarsInfo
        self.Info = aInfo

        # Get stars from StarsInfo - it's a string of star ids, separated by comma
        self.Stars = []
        if self.StarsInfo != "":
            a = self.StarsInfo.split(",")
            for id in a:
                self.Stars.append(int(id))
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Star class
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class CStar(CSpaceObject):
    #---------------------------------------------------------------------------
    def __init__(self, aId = 0,
                       aName = "",
                       aX = 0,
                       aY = 0,
                       aSize = 1,
                       aResourceFactor = 0,
                       aLiveConditions = 0,
                       aPopulation = 0,
                       aMaterials = 0,
                       aIndustry = 0,
                       aOwner = "",
                       aLastKnownAt = 0,
                       aGalaxyName = "",
                       aIsBanned = False,
                       aIsBombingBanned = False):
        CSpaceObject.__init__(self, aId, aX, aY)

        self.Name = aName
        self.Size = float(aSize)
        self.ResourceFactor = float(aResourceFactor)
        self.LiveConditions = float(aLiveConditions)
        self.Population = float(aPopulation)
        self.Materials  = float(aMaterials)
        self.Industry = float(aIndustry)
        self.Owner = aOwner
        self.LastKnownAt = int(aLastKnownAt)
        self.GalaxyName = aGalaxyName
        # List of battle sequences (CBattleSequence) might have happen in the star system
        self.BattleSequences = []
        # List of bombings for this star
        self.BombRecords = []
        # List of ground battles
        self.GroundBattles = []
        # List of assigned prod orders
        self.__prodOrders = None
        # Ownership
        self.__ownership = None
        # Restricted access
        self.__isBanned = aIsBanned
        self.__isBombingBanned = aIsBombingBanned
        # Full name
        self.__fullName = None
        # Scale factor
        self.__scaleFactor = CalcSizeFactor(self.Size, 3)
        # Flag of sector mark star
        self.__isSectorMarkStar = None
        # Container of all launched ships to the star
        self.__allLaunchedShips = None

    #---------------------------------------------------------------------------
    # Helper: returns a string field, wich can be empty or "?" what means 'empty'
    def __GetStrAttr(self, aAttr):
        return "" if aAttr == "?" else aAttr
        
    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def CalcOnScreenDiameter(self, aCalcSizeFunc):
        return aCalcSizeFunc(self.__scaleFactor, 10)

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def GetDescription(self):
        starStatus = "Unknown"
        if self.IsOwn():
            starStatus = guApp.Application.GetReport().NationName
        elif self.IsKnown() or guApp.Application.GetReport().IsGM():
            starStatus = "Uninhabited" if self.Owner == "?" else self.Owner
        return starStatus + " star system " + self.GetLocationStr() + " " + \
                             self.GetFullName() + ((" (" + self.GalaxyName + ") ") if self.GalaxyName != "" else "") + \
                             ("" if starStatus == "Unknown" else (\
                             ": " + \
                             " Size=" + str(self.Size) + \
                             ", Pop=" + "{0:.2f}".format(self.CalcActivePopulation()) + \
                             ", Col=" + "{0:.2f}".format(self.CalcCol()) + \
                             ", Ind=" + "{0:.2f}".format(self.Industry) + \
                             ", Mat=" + "{0:.2f}".format(self.Materials) + \
                             ", LiveCond=" + str(self.LiveConditions) + \
                             ", Res=" + str(self.ResourceFactor) \
                             )) + \
                             (" (NO BOMBING)" if self.IsBombingBanned() else "") + \
                             (" [ACCESS RESTRICTED]" if self.IsBanned() else "")

    #---------------------------------------------------------------------------
    # Redefined virtual from CSpaceObject
    def GetShortDesc(self):
        return self.GetIdName()

    #---------------------------------------------------------------------------
    # Returns Name
    def GetName(self):
        return self.__GetStrAttr(self.Name)
        
    #---------------------------------------------------------------------------
    # Returns 'full name' i.e. name and Id
    def GetFullName(self):
        if self.__fullName is None:
            name = self.GetName()
            self.__fullName = \
                (self.GetIdName() if name == "" else name + " (" + self.GetIdName() + ")") + \
                guApp.Application.GetReport().FindStarAMPrefix(self.Id)
        return self.__fullName
        
    #---------------------------------------------------------------------------
    # Returns owner
    def GetOwner(self):
        return self.__GetStrAttr(self.Owner)
        
    
    #---------------------------------------------------------------------------
    def IsKnown(self):
        return self.LastKnownAt > 0

    #---------------------------------------------------------------------------
    def IsActualKnown(self):
        return \
            self.IsKnown() and \
            self.LastKnownAt == guApp.Application.GetReport().Turn and \
            not guApp.Application.GetReport().IsGM()

    #---------------------------------------------------------------------------
    def IsOwn(self):
        return self.Owner == guApp.Application.GetReport().NationName and not guApp.Application.GetReport().IsGM()

    #---------------------------------------------------------------------------
    # Returns true if it's a special sector mark star'
    def IsSectorMark(self):
        if self.__isSectorMarkStar is None:
            self.__isSectorMarkStar = \
                self.GalaxyName == guApp.Application.API.guGetSectorMarkStarGalaxyName()
        return self.__isSectorMarkStar

    #---------------------------------------------------------------------------
    def IsBanned(self):
        return self.__isBanned

    #---------------------------------------------------------------------------
    def IsBombingBanned(self):
        return self.__isBombingBanned

    #---------------------------------------------------------------------------
    # Returns star ownership
    def GetOwnership(self):
        if self.__ownership is None:
            self.__ownership = guReport.CStarOwnership(self)
        return self.__ownership
        
    #---------------------------------------------------------------------------
    # Checks is star investigated
    def IsInvestigated(self):
        ownership = self.GetOwnership()
        return ownership.IsOwn or ownership.IsKnown or guApp.Application.GetReport().IsGM()
        
    #---------------------------------------------------------------------------
    # returns how many years ago the star was last reported
    def CalcLastReportedAgo(self):
        return guApp.Application.GetReport().Turn - self.LastKnownAt

    #---------------------------------------------------------------------------
    # Calculates Active population
    def CalcActivePopulation(self):
        return self.Population if self.Population <= self.Size else self.Size

    #---------------------------------------------------------------------------
    # Calculates COL
    def CalcCol(self):
        return (self.Population - self.Size)/guApp.Application.API.guColFactor() if self.Population > self.Size else 0
    
    #---------------------------------------------------------------------------
    # Calculates star's colour. Returns pygame.Color object
    def CalcColour(self):
        # Colour consists of 3 parts: r - depends on Size, g - on live conditions, b - resources
        # Normal star sizes are <= 2500, giants are up to 10000. To get colour depending on size correctly we have this logic
        return pygame.Color(int(255.0*self.Size/(2500 if self.Size <= 2500 else 10000)), 
                            int(255.0*self.LiveConditions), 
                            int(255.0*self.ResourceFactor))
    
    #---------------------------------------------------------------------------
    # Returns true if battle has happened on this star
    def HasBattle(self):
        return len(self.BattleSequences) > 0

    #---------------------------------------------------------------------------
    # Returns true if bombing has happened on this star
    def HasBombing(self):
        return len(self.BombRecords) > 0

    #---------------------------------------------------------------------------
    # Returns true if ground battles happend
    def HasGroundBattles(self):
        return len(self.GroundBattles) > 0
        

    #---------------------------------------------------------------------------
    # Returns prod orders assigned to the star
    def GetProdOrders(self):
        if self.__prodOrders is None:
            self.__prodOrders = guProdOrdersListView.GetStarProdOrders(self.Id)
        return self.__prodOrders
        

    #---------------------------------------------------------------------------
    # Redefined from CSpaceObject
    def GetPopupMenu(self, aParent):
        menu = wx.Menu()
        prodOrders = self.GetProdOrders()
        if self.HasBattle():
            mi = wx.MenuItem(menu, wx.NewId(), 'Show battle')
            aParent.Bind(wx.EVT_MENU, lambda event: self.__OnShowBattle(event, aParent), mi)
            menu.AppendItem(mi)
        if self.HasGroundBattles():
            mi = wx.MenuItem(menu, wx.NewId(), 'Ground Battles')
            aParent.Bind(wx.EVT_MENU, lambda event: guGBView.ShowGroundBattles(self.Id), mi)
            menu.AppendItem(mi)
        
        #!!!TODO Test only - hard-coded battle simulation
        #"""
        mi = wx.MenuItem(menu, wx.NewId(), 'Simulate battle')
        aParent.Bind(wx.EVT_MENU, lambda event: self.__OnSimulateBattle(event, aParent), mi)
        menu.AppendItem(mi)
        #"""
        if len(prodOrders) > 0:
            mi = wx.MenuItem(menu, wx.NewId(), 'Production orders')
            aParent.Bind(\
                wx.EVT_MENU, 
                lambda event: guProdOrdersListView.ShowProdOrdersList(\
                    "Production Orders for " + \
                    guApp.Application.GetReport().GetStarName(self.Id), self.Id), mi)
            menu.AppendItem(mi)

        # Menu for all ships on the star system
        # Note: we must provide 'creator', not pre-created list of ships to have fresh
        # data when report reloaded
        ships = guApp.Application.GetReport().FindShipsForLocation(self.X, self.Y)
        if len(ships) > 0:
            mi = wx.MenuItem(menu, wx.NewId(), 'Show all ships on the Orbit')
            aParent.Bind(\
                wx.EVT_MENU, 
                lambda event: guShipsView.ShipsList(\
                    lambda: guApp.Application.GetReport().FindShipsForLocation(self.X, self.Y),
                    "All ships on the orbit of " + \
                    guApp.Application.GetReport().GetStarName(self.Id)),
                    mi)
            menu.AppendItem(mi)

        if len(zx.GetSubListFor(\
                    guApp.Application.GetReport().SpaceGroups,
                    lambda sg: sg.Destination == self.Id)) > 0:
            mi = wx.MenuItem(menu, wx.NewId(), 'Show all approaching space groups')
            aParent.Bind(\
                wx.EVT_MENU, 
                lambda event: guSpaceGroupsListView.ShowSpaceGroups(\
                    lambda: zx.GetSubListFor(\
                        guApp.Application.GetReport().SpaceGroups,
                        lambda sg: sg.Destination == self.Id),
                    "Space Groups approaching " + guApp.Application.GetReport().GetStarName(self.Id),
                    "Own"),
                mi)
            menu.AppendItem(mi)

        def checkDestinationStarFunc(aShip):
            if not aShip.InSpace:
                star = aShip.GetDestinationStar()
                return False if star is None else star.Id == self.Id
            return False
        allLaunchedShips = zx.GetSubListFor(\
            guApp.Application.GetReport().Ships,
            lambda ship: checkDestinationStarFunc(ship))
        if len(allLaunchedShips):
            mi = wx.MenuItem(menu, wx.NewId(), 'Show all ships launched to the star')
            aParent.Bind(\
                wx.EVT_MENU, 
                lambda event: guShipsView.ShipsList(\
                    lambda: zx.GetSubListFor(\
                        guApp.Application.GetReport().Ships,
                        lambda ship: checkDestinationStarFunc(ship)),
                    "All ships launched to " + guApp.Application.GetReport().GetStarName(self.Id),
                    "LaunchedShips"),
                mi)
            menu.AppendItem(mi)            
        
        return None if menu.GetMenuItemCount() == 0 else menu

    #---------------------------------------------------------------------------
    def __OnShowBattle(self, event, aParent):
        guBattle.ViewBattle(self, self.BattleSequences, {})
        event.Skip()
        
    #---------------------------------------------------------------------------
    # Test hard coded battle simulation
    def __OnSimulateBattle(self, event, aParent):
        b1 = guBattle.CBattleSimulator()
        b1.AddDesc("Ants", "Battle Ship", "D40,S5,L10x5,M11x7@am/5/0/0.3", 15)
        b1.AddDesc("Bugs", "Winner", "D100,S50,L10x3,M10x5@am/5/0/0.3", 10)
        b1.AddDesc("Vega", "Death Star", "D500,S100,L50x5,M20x10@am/5/0/0.3", 4)
        b1.AddDesc("Zilons", "Killer", "D500,S200,L100x3,M50x5@as/5,L30x5@as,L5x5@am", 5)
        b1.AddDesc("WWW", "Killer", "D500,S300,L100x3,M100x5@as/5/0/0.2,L10x5@am, M20x4@am/5/1/0.2", 4)
        b1.AddDesc("QWE", "Scouts", "D1", 20)

        b2 = guBattle.CBattleSimulator()
        b2.AddDesc("Ants", "Battle Ship", "D40,S5,L10x1", 3)
        b2.AddDesc("Ants", "dk", "D1", 40)
        b2.AddDesc("Ants", "Rocket Ship", "D40,S5,M3x10@as/5/0/0.3", 3)
        b2.AddDesc("Bugs", "Winner", "D100,C100,S40,M15x3@as, M1x10@am/5", 5)
#        b2.AddDesc("Bugs", "Winner", "D50,S10,L10x3", 20)

        b3 = guBattle.CBattleSimulator()
        b3.AddDesc("Ants", "Battle Ship", "D100,S300,L100x20,L100x20", 20)
        b3.AddDesc("Bugs", "Winner", "D1000,S1000,L60x20,L50x20,L200x20,L500x10", 1)

        # Only missiles and 1 cannon
        b4 = guBattle.CBattleSimulator()
        b4.AddDesc("Ants", "Rocket Ship", "D2000,S500,M600x2@as/5,L200x1@am", 3)
        b4.AddDesc("Bugs", "Missile Ship", "D2000,S600,M500x2@as/5,L200x1@am", 3)
        b4.AddDesc("Bonns", "Missile Ship", "D2000,S600,M500x2@as/5,L200x1@am", 3)

        # Only missiles
        b5 = guBattle.CBattleSimulator()
        b5.AddDesc("Ants", "Rocket Ship", "D200,S50,M60x3@as/5", 3)
        b5.AddDesc("Bugs", "Missile Ship", "D200,S60,M50x3@as/5", 3)
        b5.AddDesc("Bonns", "Missile Ship", "D200,S60,M30x5@as/5", 3)

        # Algebar
        b6 = guBattle.CBattleSimulator()
        b6.AddDesc("Algebar", "WarShip", "D451,L90x9", 1)
        b6.AddDesc("Cornwal", "swarm", "D19,L20x2@as", 20)
        #b6.AddDesc("Cornwal", "Minimi", "D500.0,S300.0,C1.0,L80.0x10@as,L5.0x5@am,M20.0x10@as/3/0/0.5,R20.0", 1);

        # Only missiles & anti-missiles
        b7 = guBattle.CBattleSimulator()
        b7.AddDesc("Ants", "Rocket Ship", "D200,S300,M30x3@as/5, M5x3@am/5, L100x1", 5)
        b7.AddDesc("Bugs", "Missile Ship", "D200,S300,M30x3@as/5, M5x3@am/5, L100x1", 5)

        b7.Simulate(self)
        event.Skip()
        
#<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

    
