###############################################################################
# File:        guGroundBattle.py
# Appointment: Ground Battle
# Project:     GalaxyU
# Author:      AR
# History:
# 22.11.2012 Created
###############################################################################
import sys, pygame, os, math, copy
import random
from ctypes import *

import zx
import pgTools
import pgViewer

import guApp
import guObjects
import guShipLayout
import guStarDust
import guTools


#==============================================================================
# GroundBattle record
#==============================================================================
class CGroundBattleRecord:
    class CShotResult:
        def __init__(self, aId1 = 0, aId2 = 0, aIsDestroyed = False):
            self.Id1 = int(aId1)
            self.Id2 = int(aId2)
            self.IsDestroyed = aIsDestroyed
            self.IsOwner = False # flag is the Owner or Invader unit

    class CMiniBattle:
        def __init__(self):
            self.InvaderUnits = [] # invader units ids
            self.OwnerUnits = []   # owner units ids
            self.ShotResults = []  # shot results

    #---------------------------------------------------------------------------
    def __init__(self,
                 aStarId = 0, 
                 aInvaderNationName = "",
                 aOwnerNationName   = "",
                 aInvaderInitialCol = 0,
                 aOwnerInitialCol   = 0,
                 aInvaderResultCol  = 0,
                 aOwnerResultCol    = 0):
        self.StarId            = int(aStarId)
        self.InvaderNationName = aInvaderNationName
        self.OwnerNationName   = aOwnerNationName
        self.InvaderInitialCol = float(aInvaderInitialCol)
        self.OwnerInitialCol   = float(aOwnerInitialCol)
        self.InvaderResultCol  = float(aInvaderResultCol)
        self.OwnerResultCol    = float(aOwnerResultCol)
        self.MiniBattles       = []
#------------------------------------------------------------------------------


#==============================================================================
# Ground Battle Viewer
#==============================================================================
class CGBViewer(pgViewer.CViewer):
    TROOPS_ROWS = 25 # number of rows for army battle order
    TROOPS_COLS = 40 # number of cols for army battle order
    
    #--------------------------------------------------------------------------
    # aSequences - battle sequences
    # aShipImages - pre-set ship images map <Unit_Id : (image, colour)>
    def __init__(self, aGBRecord, aStar, aPlanetImage, aStarImage, aCfgPath, aCfgName):
        pgViewer.CViewer.__init__(\
            self, guApp.Application.Config, aCfgPath, aCfgName, 1, 0, 0, 30, 30, 1300, 800)
        pygame.display.set_caption(\
            "Ground Battle on " + aGBRecord.OwnerNationName + \
            "' colony " + guApp.Application.GetReport().GetStarName(aStar.Id))
        self.__star = aStar
        self.__GBRecord = aGBRecord
        k = 1.5
        self.__planetImage = pygame.transform.scale(aPlanetImage, (int(self.MaxWidth*k), int(self.MaxWidth*k)))
        self.__starImage = aStarImage
        
        # Disable scaling
        self.ScalingEnabled = False
        
        # Disable scroll
        self.ScrollEnabled = False
    
        # Star dust
        random.seed(self.__star.Id)
        self.__starDust = guStarDust.Generate(1000, zx.CArea(0, 0, self.MaxWidth, self.MaxHeight))
        
        # Detect images
        if guApp.Application.GetReport().NationName == self.__GBRecord.OwnerNationName:
            self.__ownerImage = guApp.Application.PopulationImages.Images[1] # white
            self.__invaderImage = guApp.Application.PopulationImages.Images[0] # red
            self.__ownerStateColour = guApp.Application.GetReport().NATION_COLOUR
            self.__invaderStateColour = guApp.Application.GetReport().ENEMY_COLOUR
        elif guApp.Application.GetReport().NationName == self.__GBRecord.InvaderNationName:
            self.__ownerImage = guApp.Application.PopulationImages.Images[0] # red
            self.__invaderImage = guApp.Application.PopulationImages.Images[1] # white
            self.__ownerStateColour = guApp.Application.GetReport().ENEMY_COLOUR
            self.__invaderStateColour = guApp.Application.GetReport().NATION_COLOUR
        else:
            self.__ownerImage = guApp.Application.PopulationImages.Images[2] # blue
            self.__invaderImage = guApp.Application.PopulationImages.Images[1] # white
            self.__ownerStateColour = guApp.Application.GetReport().NEUTRAL_COLOUR
            self.__invaderStateColour = guApp.Application.GetReport().NEUTRAL_COLOUR

        # Areas occupied on the screen by 'armies'
        self.__ownerArmyArea = zx.CArea(\
            20, int(self.MaxHeight/4) + 20,
            int(self.MaxWidth/2) - 60, int((self.MaxHeight/3)*2))
        self.__invaderArmyArea = zx.CArea(\
            int(self.MaxWidth/2) + 40, self.__ownerArmyArea.GetTop(),
            self.__ownerArmyArea.GetWidth(), self.__ownerArmyArea.GetHeight())

        # Scale images to fit the area allocated for 'army' on the screen
        # It is supposed to place 40 men in a row and have 25 rows
        self.__troopImageWidth = int(self.__ownerArmyArea.GetWidth()/self.TROOPS_COLS)
        self.__troopImageHeight = int(self.__ownerArmyArea.GetHeight()/self.TROOPS_ROWS)
        self.__ownerImage = pygame.transform.scale(\
            self.__ownerImage, (self.__troopImageWidth, self.__troopImageHeight))
        self.__invaderImage = pygame.transform.scale(\
            self.__invaderImage, (self.__troopImageWidth, self.__troopImageHeight))
        
        self.__currentMBIndex = 0 # Current Minibattle index
        self.__currentMB = None # Current minibattle object
        self.__currentShot = 0 # Current shot
        self.__totalOwnerKilled = 0 # owner killed in MB
        self.__totalInvaderKilled = 0 # invader killed in MB
        self.__started = False

        # Info font
        self.__infoFont = pgTools.LoadFont("Fonts/SelfDestructButtonBB_reg.ttf", 20)
        self.__headerFont = pgTools.LoadFont("Fonts/SelfDestructButtonBB_reg.ttf", 25)
        self.__troopsInfoFont = self.__infoFont
        
        # Flags
        self.__PlayStarted = False
        self.__DontShowMissed = self.Cfg.GetValue("DontShowMissed", "0") == "1"
        
        # Make 1st MB ready
        self.PrepareMinibattle()

    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Redefined virtual
    def OnClose(self):
        self.Cfg.SetValue("DontShowMissed", "1" if self.__DontShowMissed else "0")

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    # Helper - places aUnits (list of ids) on a screem
    # if aStartFromRight is True, the troops are placed from the right side of the area, otherwise - from the left side
    def __PlaceTroopsOnScreen(self, aImage, aArea, aUnits, aStartFromRight):
        unitIndex = 0
        for col in range (0, self.TROOPS_COLS):
            for row in range (0, self.TROOPS_ROWS):
                if unitIndex >= len(aUnits):
                    return # finished
                intent = col*self.__troopImageWidth + self.__troopImageWidth/2
                self.AddObject(\
                    aUnits[unitIndex], aImage, 
                    ((aArea.GetRight() - intent) if aStartFromRight else (aArea.GetLeft() + intent), 
                    aArea.GetTop() + row*self.__troopImageHeight - self.__troopImageHeight), self.__troopImageWidth)
                unitIndex += 1


    #--------------------------------------------------------------------------
    # Preparing minibattle
    def PrepareMinibattle(self):
        if self.__currentMBIndex < len(self.__GBRecord.MiniBattles):
            self.ResetObjects()
            self.__currentMB = self.__GBRecord.MiniBattles[self.__currentMBIndex]
            mb = self.__currentMB
            
            # Add troops objects
            self.__PlaceTroopsOnScreen(self.__ownerImage, self.__ownerArmyArea, mb.OwnerUnits, True)
            self.__PlaceTroopsOnScreen(self.__invaderImage, self.__invaderArmyArea, mb.InvaderUnits, False)
            
            # Set shot results ownership
            isOwnerLst = len(mb.OwnerUnits) < len(mb.InvaderUnits)
            lst = mb.OwnerUnits if isOwnerLst else mb.InvaderUnits
            for shot in mb.ShotResults:
                r = zx.Find(lst, lambda a: a == shot.Id1) is not None
                shot.IsOwner = r if isOwnerLst else not r
        else:
            self.__currentMB = None
            

    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    # Draws current information about the troops
    def __DrawTroopsInfo(self):
        text = " Totally killed " + str(self.__totalOwnerKilled) + ", remaining " + str(math.ceil(self.__GBRecord.OwnerInitialCol) - self.__totalOwnerKilled)
        renderText = self.__troopsInfoFont.render(text, True, self.__ownerStateColour)
        rect = renderText.get_rect()
        rect.right = self.__ownerArmyArea.GetRight() - 5
        rect.top = self.__ownerArmyArea.GetBottom()
        self.Screen.blit(renderText, rect)

        text = " Totally killed " + str(self.__totalInvaderKilled) + ", remaining " + str(math.ceil(self.__GBRecord.InvaderInitialCol) - self.__totalInvaderKilled)
        renderText = self.__troopsInfoFont.render(text, True, self.__invaderStateColour)
        rect = renderText.get_rect()
        rect.left = self.__invaderArmyArea.GetLeft() + 5
        rect.top = self.__ownerArmyArea.GetBottom()
        self.Screen.blit(renderText, rect)


    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Redefined virtual: info/control panel
    def DrawTopInfo(self, aRects):
        self.DrawInfoRects(aRects, (20,0,20))
        if self.__currentMB is None:
            # The battle is over - type result
            if self.__GBRecord.OwnerResultCol != 0:
                # Owner has won
                text = self.__GBRecord.OwnerNationName + " has repulsed attack of " + self.__GBRecord.InvaderNationName + " destroying " + str(self.__totalInvaderKilled) + " enemy units"
                colour = self.__ownerStateColour
            else:
                # Invader has won
                text = self.__GBRecord.InvaderNationName + " has successfully invaded a " + self.__GBRecord.OwnerNationName + "'s colony" + " destroying " + str(self.__totalOwnerKilled) + " enemy units"
                colour = self.__invaderStateColour
            renderText = self.__headerFont.render(text, True, colour)
            rect = renderText.get_rect()
            rect.center = aRects[1].center
            rect.top = aRects[1].top
            self.Screen.blit(renderText, rect)


    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Redefined virtual: info/control panel
    def DrawBottomInfo(self, aRects):
        # Back ground
        self.DrawInfoRects(aRects, (20,0,20))

        # Draw current battle state
        if self.__currentMB is not None:
            s = "ENTER/ESC-stop" if self.__PlayStarted else "ENTER-start"
            s += " " + ("SPACE-show hits and shield" if self.__DontShowMissed else "SPACE-show hits only")
        else:
            s = "ESC-quit"
        rect = pgTools.DrawText(self.Screen, s, 0, aRects[0].top + aRects[0].height/2,
                                self.__infoFont, guApp.Application.GetReport().NEUTRAL_COLOUR,
                                False, True)
                         
        if self.__DontShowMissed:
            s = "<HITS>"
        else:
            s = "<HITS/SHIELDS>"
        pgTools.DrawText(self.Screen, s, rect.width + 10, aRects[0].top + aRects[0].height/2,
                         self.__infoFont, (255, 255, 255),
                         False, True)

        self.__DrawTroopsInfo()


    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Redefined virtual - Draws background
    def DrawBackground(self):
        # Background
        self.Screen.fill((0,0,0))
        
        # Stardust
        guStarDust.Draw(self.__starDust, self.Screen)

        # Draw the planet
        planetRect = self.__planetImage.get_rect();
        planetRect.centerx = self.MaxWidth/2
        planetRect.centery = int(self.MaxHeight*1.2)
        self.Screen.blit(self.__planetImage, planetRect)

        # Draw star
        starRect = self.__starImage.get_rect()
        starRect.right = self.MaxWidth - 10
        starRect.top = 50
        self.Screen.blit(self.__starImage, starRect)
        
        # Draw nation flags above battle areas
        guTools.DrawNationFlag(
            self.Screen, self.__GBRecord.OwnerNationName, 
            self.__ownerArmyArea.GetRight() - guApp.Application.GetReport().FlagTemplateWidth, 
            self.__ownerArmyArea.GetTop() - 20,
            self.__ownerStateColour,
            False)
        guTools.DrawNationFlag(self.Screen, self.__GBRecord.InvaderNationName, 
            self.__invaderArmyArea.GetLeft(), 
            self.__invaderArmyArea.GetTop() - 20,
            self.__invaderStateColour,
            True)

        # Headers
        text = self.__GBRecord.OwnerNationName + "'s troops (total: " + str(math.ceil(self.__GBRecord.OwnerInitialCol)) + ")"
        renderText = self.__headerFont.render(text, True, self.__ownerStateColour)
        rect = renderText.get_rect()
        rect.right = self.__ownerArmyArea.GetRight() - 5
        rect.top = self.__ownerArmyArea.GetTop() - rect.height - 10
        self.Screen.blit(renderText, rect)

        text = self.__GBRecord.InvaderNationName + "'s troops (total: " + str(math.ceil(self.__GBRecord.InvaderInitialCol)) + ")"
        renderText = self.__headerFont.render(text, True, self.__invaderStateColour)
        rect = renderText.get_rect()
        rect.left = self.__invaderArmyArea.GetLeft() + 5
        rect.top = self.__invaderArmyArea.GetTop() - rect.height - 10
        self.Screen.blit(renderText, rect)


    #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    # Helper - changes DontShowMissed
    def __MirrowDontShowMissed(self):
        self.__DontShowMissed = not self.__DontShowMissed
        self.DrawInfoArea()
        pygame.display.flip()
    
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Redefined event
    def OnKeyDown(self, aEvent):
        if aEvent.key == pygame.K_RETURN: # start/stop
            self.__PlayStarted = True
            self.DrawInfoArea()
            pygame.display.flip()
            
            # Battle loop
            while True:
                if self.__currentMB is not None:
                    mb = self.__currentMB
                    if self.__currentShot < len(mb.ShotResults):
                        shot = mb.ShotResults[self.__currentShot]
                        if not self.__DontShowMissed or shot.IsDestroyed:
                            bk = self.GetBackground()
                            pos1 = self.Objects[shot.Id1].Pos
                            k = int(self.__troopImageHeight*1.5)
                            pos1 = (pos1[0], pos1[1] + k)
                            pos2 = self.Objects[shot.Id2].Pos
                            pos2 = (pos2[0], pos2[1] + k)
                            guTools.ShowSmoke(self.Screen, guApp.Application.OtherImages[guApp.Application.IMAGE_LASER_FLASH], bk, pos1, self.__troopImageHeight*3, 300)
                            pygame.draw.line(self.Screen, self.__ownerStateColour if shot.IsOwner else self.__invaderStateColour, pos1, pos2)
                            pygame.display.flip()

                            if shot.IsDestroyed:
                                guTools.ShowExplosion(self.Screen, bk, pos2, self.__troopImageHeight*2, 50)
                                self.Objects[shot.Id2].IsVisible = False
                                if shot.IsOwner:
                                    self.__totalInvaderKilled += 1
                                else:
                                    self.__totalOwnerKilled += 1
                                self.ReDraw()
                            else:
                                guTools.ShowShield(self.Screen, bk, pos2, self.__troopImageHeight, guApp.Application.GetReport().NEUTRAL_COLOUR, 3, 60)
                            
                        self.__currentShot += 1
                        
                        events = pygame.event.get([pygame.KEYDOWN])
                        for e in events:
                            if e.key == pygame.K_RETURN or e.key == pygame.K_ESCAPE:
                                self.__PlayStarted = False
                                self.DrawInfoArea()
                                pygame.display.flip()
                                return
                            elif e.key == pygame.K_SPACE:
                                self.__MirrowDontShowMissed()
                                
                    else:
                        self.__currentMBIndex += 1
                        self.__currentShot = 0
                        self.PrepareMinibattle()
                        self.ReDraw()
                else:
                    break
            return True
        elif aEvent.key == pygame.K_SPACE:
            self.__MirrowDontShowMissed()
            return True

        return False # not processed
        
#------------------------------------------------------------------------------

