# noinspection PyPackageRequirements
import wx

import gui.mainFrame
from gui.auxWindow import AuxiliaryDialog
from gui.contextMenu import ContextMenuSingle
from service.ammo import Ammo
from service.market import Market


class GraphFitAmmoPicker(ContextMenuSingle):

    def __init__(self):
        self.mainFrame = gui.mainFrame.MainFrame.getInstance()

    def display(self, callingWindow, srcContext, mainItem):
        if srcContext != 'graphFitList':
            return False
        if mainItem is None or not mainItem.isFit:
            return False
        if callingWindow.graphFrame.getView().internalName != 'dmgStatsGraph':
            return False
        return True

    def getText(self, callingWindow, itmContext, mainItem):
        return 'Plot with Different Ammo...'

    def activate(self, callingWindow, fullContext, mainItem, i):
        AmmoPickerFrame.openOne(callingWindow, mainItem.item, forceReopen=True)


# GraphFitAmmoPicker.register()


class AmmoPickerFrame(AuxiliaryDialog):

    def __init__(self, parent, fit):
        super().__init__(parent, title='Choose Different Ammo', style=wx.DEFAULT_DIALOG_STYLE, resizeable=True)
        padding = 5

        mainSizer = wx.BoxSizer(wx.VERTICAL)

        contents = AmmoPickerContents(self, fit)
        mainSizer.Add(contents, 1, wx.EXPAND | wx.ALL, padding)

        buttonSizer = self.CreateButtonSizer(wx.OK | wx.CANCEL)
        if buttonSizer:
            mainSizer.Add(buttonSizer, 0, wx.EXPAND | wx.ALL, padding)

        self.SetSizer(mainSizer)
        self.Layout()

        contW, contH = contents.GetVirtualSize()
        bestW = contW + padding * 2
        bestH = contH + padding * 2
        if buttonSizer:
            # Yeah right... whatever
            buttW, buttH = buttonSizer.GetSize()
            bestW = max(bestW, buttW + padding * 2)
            bestH += buttH + padding * 2
        bestW = min(1000, bestW)
        bestH = min(700, bestH)
        self.SetSize(bestW, bestH)
        self.SetMinSize(wx.Size(int(bestW * 0.7), int(bestH * 0.7)))
        self.CenterOnParent()
        self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)

    def kbEvent(self, event):
        if event.GetKeyCode() == wx.WXK_ESCAPE and event.GetModifiers() == wx.MOD_NONE:
            self.Close()
            return
        event.Skip()


class AmmoPickerContents(wx.ScrolledCanvas):

    indent = 15

    def __init__(self, parent, fit):
        wx.ScrolledCanvas.__init__(self, parent)
        self.SetScrollRate(0, 15)

        mods = self.getMods(fit)
        drones = self.getDrones(fit)
        fighters = self.getFighters(fit)
        self.rbLabelMap = {}
        self.rbCheckboxMap = {}

        mainSizer = wx.BoxSizer(wx.VERTICAL)

        moduleSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(moduleSizer, 0, wx.ALL, 0)

        self.droneSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(self.droneSizer, 0, wx.ALL, 0)

        fighterSizer = wx.BoxSizer(wx.VERTICAL)
        mainSizer.Add(fighterSizer, 0, wx.ALL, 0)

        firstRadio = True

        for modInfo, modAmmo in mods:
            text = '\n'.join('{}x {}'.format(amount, item.name) for item, amount in modInfo)
            modRb = self.addRadioButton(moduleSizer, text, firstRadio)
            firstRadio = False
            # Get actual module, as ammo getters need it
            mod = next((m for m in fit.modules if m.itemID == next(iter(modInfo))[0].ID), None)
            _, ammoTree = Ammo.getInstance().getModuleStructuredAmmo(mod)
            if len(ammoTree) == 1:
                for ammoCatName, ammos in ammoTree.items():
                    for ammo in ammos:
                        self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
            else:
                for ammoCatName, ammos in ammoTree.items():
                    if len(ammos) == 1:
                        ammo = next(iter(ammos))
                        self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=1)
                    else:
                        self.addLabel(moduleSizer, '{}:'.format(ammoCatName), modRb, indentLvl=1)
                        for ammo in ammos:
                            self.addCheckbox(moduleSizer, ammo.name, modRb, indentLvl=2)
        if drones:
            droneRb = self.addRadioButton(self.droneSizer, 'Drones', firstRadio)
            from gui.builtinAdditionPanes.droneView import DroneView
            for drone in sorted(drones, key=DroneView.droneKey):
                self.addCheckbox(self.droneSizer, '{}x {}'.format(drone.amount, drone.item.name), droneRb, indentLvl=1)
            addBtn = wx.Button(self, wx.ID_ANY, '+', style=wx.BU_EXACTFIT)
            addBtn.Bind(wx.EVT_BUTTON, self.OnDroneGroupAdd)
            mainSizer.Add(addBtn, 0, wx.LEFT, self.indent)
        if fighters:
            fighterRb = self.addRadioButton(fighterSizer, 'Fighters', firstRadio)
            from gui.builtinAdditionPanes.fighterView import FighterDisplay
            for fighter in sorted(fighters, key=FighterDisplay.fighterKey):
                self.addCheckbox(fighterSizer, '{}x {}'.format(fighter.amount, fighter.item.name), fighterRb, indentLvl=1)

        self.SetSizer(mainSizer)
        self.refreshStatus()

    def addRadioButton(self, sizer, text, firstRadio=False):
        if firstRadio:
            rb = wx.RadioButton(self, wx.ID_ANY, text, style=wx.RB_GROUP)
            rb.SetValue(True)
        else:
            rb = wx.RadioButton(self, wx.ID_ANY, text)
            rb.SetValue(False)
        rb.Bind(wx.EVT_RADIOBUTTON, self.rbSelected)
        sizer.Add(rb, 0, wx.EXPAND | wx.ALL, 0)
        return rb

    def addCheckbox(self, sizer, text, currentRb, indentLvl=0):
        cb = wx.CheckBox(self, -1, text)
        sizer.Add(cb, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
        if currentRb is not None:
            self.rbCheckboxMap.setdefault(currentRb, []).append(cb)

    def addLabel(self, sizer, text, currentRb, indentLvl=0):
        text = text[0].capitalize() + text[1:]
        label = wx.StaticText(self, wx.ID_ANY, text)
        sizer.Add(label, 0, wx.EXPAND | wx.LEFT, self.indent * indentLvl)
        if currentRb is not None:
            self.rbLabelMap.setdefault(currentRb, []).append(label)

    def getMods(self, fit):
        sMkt = Market.getInstance()
        sAmmo = Ammo.getInstance()
        loadableChargesCache = {}
        # Modules, format: {frozenset(ammo): {item: count}}
        modsPrelim = {}
        if fit is not None:
            for mod in fit.modules:
                if not mod.canDealDamage():
                    continue
                typeID = mod.item.ID
                if typeID not in loadableChargesCache:
                    loadableChargesCache[typeID] = sAmmo.getModuleFlatAmmo(mod)
                charges = loadableChargesCache[typeID]
                # We're not interested in modules which contain no charges
                if charges:
                    data = modsPrelim.setdefault(frozenset(charges), {})
                    if mod.item not in data:
                        data[mod.item] = 0
                    data[mod.item] += 1
        # Format: [([(item, count), ...], frozenset(ammo)), ...]
        modsFinal = []
        for charges, itemCounts in modsPrelim.items():
            modsFinal.append((
                # Sort items within group
                sorted(itemCounts.items(), key=lambda i: sMkt.itemSort(i[0], reverseMktGrp=True), reverse=True),
                charges))
        # Sort item groups
        modsFinal.sort(key=lambda i: sMkt.itemSort(i[0][0][0], reverseMktGrp=True), reverse=True)
        return modsFinal

    def getDrones(self, fit):
        drones = []
        if fit is not None:
            for drone in fit.drones:
                if drone.item is None:
                    continue
                # Drones are our "ammo", so we want to pick even those which are inactive
                if drone.canDealDamage(ignoreState=True):
                    drones.append(drone)
                    continue
                if {'remoteWebifierEntity', 'remoteTargetPaintEntity'}.intersection(drone.item.effects):
                    drones.append(drone)
                    continue
        return drones

    def getFighters(self, fit):
        fighters = []
        if fit is not None:
            for fighter in fit.fighters:
                if fighter.item is None:
                    continue
                # Fighters are our "ammo" as well
                if fighter.canDealDamage(ignoreState=True):
                    fighters.append(fighter)
                    continue
                for ability in fighter.abilities:
                    if not ability.active:
                        continue
                    if ability.effect.name == 'fighterAbilityStasisWebifier':
                        fighters.append(fighter)
                        break
        return fighters

    def OnDroneGroupAdd(self, event):
        event.Skip()
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        label = wx.StaticText()
        self.droneSizer.Add(sizer, 0, wx.EXPAND | wx.LEFT, self.indent)

    def refreshStatus(self):
        for map in (self.rbLabelMap, self.rbCheckboxMap):
            for rb, items in map.items():
                for item in items:
                    item.Enable(rb.GetValue())

    def rbSelected(self, event):
        event.Skip()
        self.refreshStatus()
