# vim:ts=4:et
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program 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 2
#  of the License, or (at your option) any later version.
#
#  This program 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 this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

import os
from .script import Script
from .qfplist import pldata
from . import quakechr

MAX_FLAGS = 8

class EntityClassError(Exception):
    def __init__(self, fname, line, message):
        Exception.__init__(self, "%s:%d: %s" % (fname, line, message))
        self.line = line

def entclass_error(self, msg):
    raise EntityClassError(self.filename, self.line, msg)

class EntityClass:
    def __init__(self, name, color, size, flagnames, comment):
        self.name = name
        self.color = color
        self.size = size
        self.flagnames = flagnames
        self.comment = comment
    @classmethod
    def null(cls):
        return cls('', (1, 1, 1), None, (), "")
    @classmethod
    def from_quaked(cls, text, filename, line = 0):
        script = Script(filename, text)
        script.error = entclass_error.__get__(script, Script)
        if line:
            script.line = line
        script.getToken()   # skip over the leading '/*QUAKED'
        name = script.getToken()
        color = cls.parse_vector(script)
        if script.tokenAvailable():
            size = cls.parse_size(script)
            flagnames = cls.parse_flags(script)
        else:
            size = None
            flagnames = ()
        comment = cls.extract_comment(script)
        return cls(name, color, size, flagnames, comment)
    @classmethod
    def from_dictionary(cls, name, d):
        if "color" in d:
            color = d["color"]
            color = float(color[0]), float(color[1]), float(color[2])
        else:
            color = (0.0, 0.0, 0.0)
        if "size" in d:
            mins, maxs = d["size"]
            size = ((float(mins[0]), float(mins[1]), float(mins[2])),
                    (float(maxs[0]), float(maxs[1]), float(maxs[2])))
        else:
            size = None
        if "flagnames" in d:
            flagnames = tuple(d["flagnames"])
        else:
            flagnames = ()
        if "comment" in d:
            comment = d["comment"]
        else:
            comment = ""
        return cls(name, color, size, flagnames, comment)
    def to_dictionary(self):
        d = {"color":self.color, "flagnames":self.flagnames,
             "comment":self.comment}
        if self.size:
            d["size"] = self.size
        return d
    @classmethod
    def parse_vector(cls, script):
        if script.getToken() != "(":
            script.error("Missing (")
        v = (float(script.getToken()), float(script.getToken()),
             float(script.getToken()))
        if script.getToken() != ")":
            script.error("Missing )")
        return v
    @classmethod
    def parse_size(cls, script):
        if script.getToken() == "?":
            return None                 # use brush size
        elif script.token == "(":
            script.ungetToken()
            return cls.parse_vector(script), cls.parse_vector(script)
        else:
            script.ungetToken()
            return None
    @classmethod
    def parse_flags(cls, script):
        flagnames = []
        while script.tokenAvailable():
            #any remaining words on the line are flag names, but only MAX_FLAGS
            #names are kept.
            script.getToken()
            if len(flagnames) < MAX_FLAGS:
                flagnames.append(script.token)
        return tuple(flagnames)
    @classmethod
    def extract_comment(cls, script):
        if not script.tokenAvailable(True):
            return ""
        start = pos = script.pos
        while pos < len(script.text) and script.text[pos:pos + 2] != "*/":
            if script.text[pos] == "\n":
                script.line += 1
            pos += 1
        comment = script.text[start:pos]
        if pos < len(script.text):
            pos += 2
        script.pos = pos
        return comment

class EntityClassDict:
    def __init__(self):
        self.path = ""
        self.entity_classes = {}
    def __len__(self):
        return self.entity_classes.__len__()
    def __getitem__(self, key):
        return self.entity_classes.__getitem__(key)
    def __iter__(self):
        return self.entity_classes.__iter__()
    def __contains__(self, item):
        return self.entity_classes.__contains__(item)
    def keys(self):
        return self.entity_classes.keys()
    def values(self):
        return self.entity_classes.values()
    def items(self):
        return self.entity_classes.items()
    def get(self, key, default=None):
        return self.entity_classes.get(key, default)
    def scan_source(self, fname):
        text = open(fname, "rt", encoding="idquake").read()
        line = 1
        pos = 0
        while pos < len(text):
            if text[pos:pos + 8] == "/*QUAKED":
                start = pos
                start_line = line
                while pos < len(text) and text[pos:pos + 2] != "*/":
                    if text[pos] == "\n":
                        line += 1
                    pos += 1
                if pos < len(text):
                    pos += 2
                ec = EntityClass.from_quaked(text[start:pos], fname,
                                             start_line)
                self.entity_classes[ec.name] = ec
            else:
                if text[pos] == "\n":
                    line += 1
                pos += 1
    def scan_directory(self, path):
        files = os.listdir(path)
        files.sort()
        for f in files:
            if f[0] in [".", "_"]:
                continue
            if os.path.isdir(os.path.join(path, f)):
                self.scan_directory(os.path.join(path, f))
            else:
                if f[-3:] == ".qc":
                    self.scan_source(os.path.join(path, f))
    def from_source_tree(self, path):
        self.path = path
        self.entity_classes = {}
        self.scan_directory(self.path)
    def to_plist(self):
        pl = pldata()
        ec = {}
        for k in self.entity_classes.keys():
            ec[k] = self.entity_classes[k].to_dictionary()
        return pl.write(ec)
    def from_plist(self, plist):
        pl = pldata(plist)
        ec = pl.parse()
        self.entity_classes = {}
        for k in ec.keys():
            self.entity_classes[k] = EntityClass.from_dictionary(k, ec[k])