#===============================================================================
# Copyright (C) 2010 Diego Duclos
#
# This file is part of pyfa.
#
# pyfa is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyfa is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pyfa.  If not, see <http://www.gnu.org/licenses/>.
#===============================================================================

import eos.db
import eos.types
import copy
import service
import itertools
from eos import eveapi
import config
import json
import os.path
import locale
import threading
import wx
from codecs import open

from xml.etree import ElementTree
from xml.dom import minidom

import gzip

class CharacterImportThread(threading.Thread):
    def __init__(self, paths, callback):
        threading.Thread.__init__(self)
        self.paths = paths
        self.callback = callback

    def run(self):
        paths = self.paths
        sCharacter = Character.getInstance()
        for path in paths:
            with open(path, mode='r') as charFile:
                sheet = eveapi.ParseXML(charFile)
                charID = sCharacter.new()
                sCharacter.rename(charID, sheet.name+" (imported)")
                sCharacter.apiUpdateCharSheet(charID, sheet)

        wx.CallAfter(self.callback)

class SkillBackupThread(threading.Thread):
    def __init__(self, path, saveFmt, activeFit, callback):
        threading.Thread.__init__(self)
        self.path = path
        self.saveFmt = saveFmt
        self.activeFit = activeFit
        self.callback = callback

    def run(self):
        path = self.path
        sCharacter = Character.getInstance()
        sFit = service.Fit.getInstance()
        fit = sFit.getFit(self.activeFit)
        backupData = "";
        if self.saveFmt == "xml" or self.saveFmt == "emp":
            backupData = sCharacter.exportXml()
        else:
            backupData = sCharacter.exportText()

        if self.saveFmt == "emp":
            with gzip.open(path, mode='wb') as backupFile:
                backupFile.write(backupData)
        else:
            with open(path, mode='w',encoding='utf-8') as backupFile:
                backupFile.write(backupData)

        wx.CallAfter(self.callback)

class Character(object):
    instance = None
    skillReqsDict = {}

    @classmethod
    def getInstance(cls):
        if cls.instance is None:
            cls.instance = Character()

        return cls.instance

    def exportText(self):
        data  = "Pyfa exported plan for \""+self.skillReqsDict['charname']+"\"\n"
        data += "=" * 79 + "\n"
        data += "\n"
        item = ""
        for s in self.skillReqsDict['skills']:
            if item == "" or not item == s["item"]:
                item = s["item"]
                data += "-" * 79 + "\n"
                data += "Skills required for {}:\n".format(item)
            data += "{}{}: {}\n".format("    " * s["indent"], s["skill"], int(s["level"]))
        data += "-" * 79 + "\n"

        return data

    def exportXml(self):
        root = ElementTree.Element("plan")
        root.attrib["name"] = "Pyfa exported plan for "+self.skillReqsDict['charname']
        root.attrib["revision"] = config.evemonMinVersion

        sorts = ElementTree.SubElement(root, "sorting")
        sorts.attrib["criteria"] = "None"
        sorts.attrib["order"] = "None"
        sorts.attrib["groupByPriority"] = "false"

        skillsSeen = set()

        for s in self.skillReqsDict['skills']:
            skillKey = str(s["skillID"])+"::"+s["skill"]+"::"+str(int(s["level"]))
            if skillKey in skillsSeen:
                pass   # Duplicate skills confuse EVEMon
            else:
                skillsSeen.add(skillKey)
                entry = ElementTree.SubElement(root, "entry")
                entry.attrib["skillID"] = str(s["skillID"])
                entry.attrib["skill"] = s["skill"]
                entry.attrib["level"] = str(int(s["level"]))
                entry.attrib["priority"] = "3"
                entry.attrib["type"] = "Prerequisite"
                notes = ElementTree.SubElement(entry, "notes")
                notes.text = entry.attrib["skill"]

        tree = ElementTree.ElementTree(root)
        data = ElementTree.tostring(root, 'utf-8')
        prettydata = minidom.parseString(data).toprettyxml(indent="  ")

        return prettydata

    def backupSkills(self, path, saveFmt, activeFit, callback):
        thread = SkillBackupThread(path, saveFmt, activeFit, callback)
        thread.start()

    def importCharacter(self, path, callback):
        thread = CharacterImportThread(path, callback)
        thread.start()

    def all0(self):
        all0 = eos.types.Character.getAll0()
        eos.db.commit()
        return all0

    def all0ID(self):
        return self.all0().ID

    def all5(self):
        all5 = eos.types.Character.getAll5()
        eos.db.commit()
        return all5

    def all5ID(self):
        return self.all5().ID

    def getCharacterList(self):
        baseChars = [eos.types.Character.getAll0(), eos.types.Character.getAll5()]
        # Flush incase all0 & all5 weren't in the db yet
        eos.db.commit()
        sFit = service.Fit.getInstance()
        return map(lambda c: (c.ID, c.name, c == sFit.character), eos.db.getCharacterList())

    def getCharacter(self, charID):
        char = eos.db.getCharacter(charID)
        return char

    def getSkillGroups(self):
        cat = eos.db.getCategory(16)
        groups = []
        for grp in cat.groups:
            if grp.published:
                groups.append((grp.ID, grp.name))
        return groups

    def getSkills(self, groupID):
        group = eos.db.getGroup(groupID)
        skills = []
        for skill in group.items:
            if skill.published == True:
                skills.append((skill.ID, skill.name))
        return skills

    def getSkillDescription(self, itemID):
        return eos.db.getItem(itemID).description

    def getGroupDescription(self, groupID):
        return eos.db.getMarketGroup(groupID).description

    def getSkillLevel(self, charID, skillID):
        skill = eos.db.getCharacter(charID).getSkill(skillID)
        return skill.level if skill.learned else "Not learned"

    def rename(self, charID, newName):
        char = eos.db.getCharacter(charID)
        char.name = newName
        eos.db.commit()

    def new(self):
        char = eos.types.Character("New Character")
        eos.db.save(char)
        return char.ID

    def getCharName(self, charID):
        return eos.db.getCharacter(charID).name

    def copy(self, charID):
        char = eos.db.getCharacter(charID)
        newChar = copy.deepcopy(char)
        eos.db.save(newChar)
        return newChar.ID

    def delete(self, charID):
        char = eos.db.getCharacter(charID)
        eos.db.commit()
        eos.db.remove(char)

    def getApiDetails(self, charID):
        char = eos.db.getCharacter(charID)
        if char.chars is not None:
            chars = json.loads(char.chars)
        else:
            chars = None
        return (char.apiID or "", char.apiKey or "", char.defaultChar or "", chars or [])

    def apiEnabled(self, charID):
        id, key, default, _ = self.getApiDetails(charID)
        return id is not "" and key is not "" and default is not ""

    def charList(self, charID, userID, apiKey):
        char = eos.db.getCharacter(charID)
        try:
            char.apiID = userID
            char.apiKey = apiKey
            charList = char.apiCharList(proxy = service.settings.ProxySettings.getInstance().getProxySettings())
            char.chars = json.dumps(charList)
            return charList
        except:
            return None

    def apiFetch(self, charID, charName):
        char = eos.db.getCharacter(charID)
        char.defaultChar = charName
        char.apiFetch(charName, proxy = service.settings.ProxySettings.getInstance().getProxySettings())
        eos.db.commit()

    def apiUpdateCharSheet(self, charID, sheet):
        char = eos.db.getCharacter(charID)
        char.apiUpdateCharSheet(sheet)
        eos.db.commit()

    def changeLevel(self, charID, skillID, level):
        char = eos.db.getCharacter(charID)
        skill = char.getSkill(skillID)
        if isinstance(level, basestring) or level > 5 or level < 0:
            skill.learned = False
            skill.level = None
        else:
            skill.level = level

        eos.db.commit()

    def addImplant(self, charID, itemID):
        char = eos.db.getCharacter(charID)
        implant = eos.types.Implant(eos.db.getItem(itemID))
        char.implants.freeSlot(implant.slot)
        char.implants.append(implant)

    def removeImplant(self, charID, slot):
        char = eos.db.getCharacter(charID)
        char.implants.freeSlot(slot)

    def getImplants(self, charID):
        char = eos.db.getCharacter(charID)
        return char.implants

    def checkRequirements(self, fit):
        toCheck = []
        reqs = {}
        for thing in itertools.chain(fit.modules, fit.drones, (fit.ship,)):
            for attr in ("item", "charge"):
                subThing = getattr(thing, attr, None)
                subReqs = {}
                if subThing is not None:
                    self._checkRequirements(fit, fit.character, subThing, subReqs)
                    if subReqs:
                        reqs[subThing] = subReqs

        return reqs

    def _checkRequirements(self, fit, char, subThing, reqs):
        for req, level in subThing.requiredSkills.iteritems():
            name = req.name
            ID = req.ID
            info = reqs.get(name)
            currLevel, subs = info if info is not None else 0, {}
            if level > currLevel and (char is None or char.getSkill(req).level < level):
                reqs[name] = (level, ID, subs)
                self._checkRequirements(fit, char, req, subs)

        return reqs
