from __future__ import generators
import plus
import AI
from AI import vector3
import Arenas
import Gooey
import math
import operator
import Tactics

class SpinupOmni2(AI.SuperAI):
    "Waits for weapon to spin up before moving.  Also reverses weapon if it gets jammed."
    name = "SpinupOmni2"
    # ***CUSTOMIZABLE SETTINGS*** #
    # 'ticks' sets how many times per tick the AI checks its RPM (Revolutions Per Minute) (a tick is 1/8 second).  Needs to be high for accurate measurements on fast spinners, but too high causes lag.  Default is 3.75 (=30 times/second).
    # ***NOTE*** RA2 can't measure times less than 1/30 second.  Therefore the RPM calculator is only accurate for speeds of 1500, 750, 500, 375, 300, and <250 RPM.  Speeds over 1500 RPM can't be measured, and accuracy decreases with increasing RPM.
    # 'MotorID' tells the AI which motor to measure RPM on.
    # 'Motor2ID' (Optional) Set ID for a second motor to measure RPM on.
    # 'TargetRPM' is the RPM the weapon needs to be spinning at before the AI will move.  (With two motors, only one needs to exceed the target RPM.)  Default is 100.
    # 'DisplayRPM' Set this to 1 to display current RPM, average RPM, and maximum RPM during battle.  Use this to determine ticks and TargetRPM values.

    def __init__(self, **args):
        AI.SuperAI.__init__(self, **args)
               
        self.zone = "weapon"
        self.triggers = ["Fire"]
        self.trigger2 = ["Srimech"]
        self.reloadTime = 0
        self.reloadDelay = 3
        self.tickFactor = 3.75
        self.spin_range = 3.0
        self.motorID = 2
        self.motorID2 = -1
        self.RPM = 0
        self.RPM2 = 0
        self.revolution = 0
        self.revolution2 = 0
        self.measuring = 0
        self.measuring2 = 0
        self.max_RPM = 0
        self.RPMhist = []
        self.avg_RPM = 0
        self.display = 0
        self.targetRPM = 100
        self.spin_reverse = 0
        self.jamTimer = 0
        self.stopFunction = self.Stop
        
        if 'range' in args:
            self.spin_range = args.get('range')
      
        if 'triggers' in args: self.triggers = args['triggers']
        if 'reload' in args: self.reloadDelay = args['reload']
        if 'ticks' in args: self.tickFactor = args['ticks']
        if 'MotorID' in args: self.motorID = args['MotorID']
        if 'Motor2ID' in args: self.motorID2 = args['Motor2ID']
        if 'DisplayRPM' in args: self.display = args['DisplayRPM']
        if 'TargetRPM' in args: self.targetRPM = args['TargetRPM']
        
        self.triggerIterator = iter(self.triggers)
 
        self.tactics.append(Tactics.Engage(self))
        
    def Activate(self, active):
        plus.AI.__setattr__tickInterval__(self, 0.125/self.tickFactor)
        if active:
            if AI.SuperAI.debugging:
                self.debug = Gooey.Plain("watch", 0, 75, 100, 75)
                tbox = self.debug.addText("line0", 0, 0, 100, 15)
                tbox.setText("Throttle")
                tbox = self.debug.addText("line1", 0, 15, 100, 15)
                tbox.setText("Turning")
                tbox = self.debug.addText("line2", 0, 30, 100, 15)
                tbox.setText("")
                tbox = self.debug.addText("line3", 0, 45, 100, 15)
                tbox.setText("")
            if self.display != 0:
                self.tauntbox = Gooey.Plain("taunt", 10, 175, 640, 175)
                tbox = self.tauntbox.addText("taunt1", 10, 0, 640, 15)
                tbox.setText("")
                tbox2 = self.tauntbox.addText("taunt2", 10, 20, 640, 15)
                tbox2.setText("")
                tbox3 = self.tauntbox.addText("taunt3", 10, 40, 640, 15)
                tbox3.setText("")
                tbox4 = self.tauntbox.addText("taunt4", 10, 60, 640, 15)
                tbox4.setText("")
            
            self.RegisterSmartZone(self.zone, 1)
        else:
            # get rid of reference to self
            self.stopFunction = None
            
        return AI.SuperAI.Activate(self, active)

    def Tick(self):
        if self.display != 0:
            self.tauntbox.get("taunt1").setText(str(self.RPM) + " RPM")
            if self.motorID2 > 0:
                self.tauntbox.get("taunt2").setText(str(self.RPM2) + " RPM (Motor 2)")
            else:
                self.tauntbox.get("taunt2").setText("No motor 2.")
            self.tauntbox.get("taunt3").setText("Average RPM: "+str(self.avg_RPM))
            self.tauntbox.get("taunt4").setText("Maximum RPM: "+str(self.max_RPM))
            
        #Measure RPM
        if (self.GetMotorAngle(self.motorID) > math.pi/3 or self.GetMotorAngle(self.motorID) < 0) and self.measuring == 0:
            self.revolution = plus.getTimeElapsed()
            self.measuring = 1
            
        if 0 < self.GetMotorAngle(self.motorID) < math.pi/3 and self.measuring == 1:
            self.RPM = 50/(plus.getTimeElapsed() - self.revolution)
            if self.display != 0:
                self.RPMhist.append(self.RPM)
                if self.RPM > self.max_RPM:
                    self.max_RPM = self.RPM
            self.measuring = 0
            
        if self.motorID2 > 0:
            #Measure RPM of second motor
            if (self.GetMotorAngle(self.motorID2) > math.pi/3 or self.GetMotorAngle(self.motorID2) < 0) and self.measuring2 == 0:
                self.revolution2 = plus.getTimeElapsed()
                self.measuring2 = 1
                
            if 0 < self.GetMotorAngle(self.motorID2) < math.pi/3 and self.measuring2 == 1:
                self.RPM2 = 50/(plus.getTimeElapsed() - self.revolution2)
                if self.display != 0:
                    self.RPMhist.append(self.RPM2)
                    if self.RPM2 > self.max_RPM:
                        self.max_RPM = self.RPM2
                self.measuring2 = 0
            
        if len(self.RPMhist) > 0:
            self.avg_RPM = reduce(operator.add, self.RPMhist)/len(self.RPMhist)
        
        # fire weapon

        # spin up depending on enemy's range
        enemy, range = self.GetNearestEnemy()
        
        if enemy is not None and range < self.spin_range:
            if self.spin_reverse == 0:
                self.Input("Spin", 0, 100)
            if self.spin_reverse == 1:
                self.Input("Spin", 0, -100)
        elif self.GetInputStatus("Spin", 0) != 0:
            self.Input("Spin", 0, 0)
            
        # Try reversing weapons if they get jammed
        if plus.getTimeElapsed() - self.revolution > 1.5 or (self.motorID2 > 0 and plus.getTimeElapsed() - self.revolution2 > 1.5):
            self.jamTimer += 1
        else:
            self.jamTimer = 0
            
        if self.jamTimer >= 24*self.tickFactor and self.spin_reverse == 0:
            self.spin_reverse = 1
            self.jamTimer = 0
            
        if self.jamTimer >= 24*self.tickFactor and self.spin_reverse == 1:
            self.spin_reverse = 0
            self.jamTimer = 0
        
        targets = [x for x in self.sensors.itervalues() if x.contacts > 0 \
            and not plus.isDefeated(x.robot)]
        
        if self.weapons:
            # slight delay between firing
            if self.reloadTime > 0: self.reloadTime -= 1
            
            if len(targets) > 0 and self.reloadTime <= 0:
                try:
                    trigger = self.triggerIterator.next()
                except StopIteration:
                    self.triggerIterator = iter(self.triggers)
                    trigger = self.triggerIterator.next()
                
                self.Input(trigger, 0, 1)
                self.reloadTime = self.reloadDelay
        
        bReturn = AI.SuperAI.Tick(self)
            
        # call this now so it takes place after other driving commands
        if self.stopFunction: self.stopFunction(len(targets) > 0)
        
        return bReturn
        
    def Stop(self, bTarget):
        # stay put if weapon isn't spinning fast
        if self.weapons and not self.bImmobile and (self.RPM < self.targetRPM or (self.motorID2 > 0 and self.RPM2 < self.targetRPM)):
            self.Throttle(0)

    def StuckHandler(self):
        "This default generator is called when the bot is almost immobile."
        while 1:
            # back up for 2 seconds (will stop once we're not immobile)
            for i in range(0, 16*self.tickFactor):
                pos = vector3(self.GetLocation())
                dir = vector3(self.GetDirection())
                self.DriveToLocation((pos - dir * 3).asTuple(), True)
                yield 0
            # go forward for 2 seconds
            for i in range(0, 16*self.tickFactor):
                pos = vector3(self.GetLocation())
                dir = vector3(self.GetDirection())
                self.DriveToLocation((pos + dir * 3).asTuple())
                yield 0
                
    def Think(self):
        self.Evaluate()
        self.countdownToEvaluation = 8*self.tickFactor
        # shut down motors while we think
        self.Throttle(0)
        self.Turn(0)
        
    def InvertHandler(self):
        # fire all weapons once per second (until we're upright!)
        while 1:
            for trigger in self.trigger2:
                self.Input(trigger, 0, 1)
            
            for i in range(0, 8*self.tickFactor):
                yield 0
                
    def LostComponent(self, id):
        # if we lose all our weapons, stop using the Engage tactic and switch to Shove
        if id in self.weapons: self.weapons.remove(id)
        
        if not self.weapons:
            tactic = [x for x in self.tactics if x.name == "Engage"]
            if len(tactic) > 0:
                self.tactics.remove(tactic[0])
                
                self.tactics.append(Tactics.Shove(self))
                self.tactics.append(Tactics.Charge(self))
            
        return AI.SuperAI.LostComponent(self, id)
                
    def DebugString(self, id, string):
        if self.debug:
            if id == 0: self.debug.get("line0").setText(string)
            elif id == 1: self.debug.get("line1").setText(string)
            elif id == 2: self.debug.get("line2").setText(string)
            elif id == 3: self.debug.get("line3").setText(string)
    
AI.register(SpinupOmni2)