import os
import re
import sys

import global_vars
from xcf import Xcfinfo

cur_dir = os.path.dirname(__file__)

xcfdir = os.path.join('src', 'xcf')
pngdir = os.path.join('build', 'png')

class Parser:
    def __init__(self, db, id_, pygfx):
        self.db = db
        assert id_ in self.db
        self.consist = self.db[id_]
        self.make_rules = []
        self.pygfx = pygfx

        # Parse custom blocks
        self.parse_parts()
        self.parse_props_override()
        self.parse_sounds()
        self.parse_sprites()
        self.parse_subtype_by_year()

        # Parse NML
        self.parse_nml()

        # Resolve the consist to it's final state
        self.consist.resolve()

    def parse_props_override(self):
        """Parse PROPS_OVERRIDE block."""
        block = re.search(r'\bPROPS_OVERRIDE\s*\{(.+?)\}',
                          self.pygfx, re.DOTALL)
        if not block:
            return
        prop_vals = block.group(1).split(';')[:-1]
        for pv in prop_vals:
            spl = pv.split(':')
            prop = spl[0].strip()
            val = spl[1].strip()
            self.consist.props[prop] = val

    def parse_sounds(self):
        """Parse SOUNDS block."""
        block = re.search(r'\bSOUNDS\s*\{(.+?)\}', self.pygfx, re.DOTALL)
        if not block:
            return
        vol = 'param_volume'
        for n, def_ in enumerate(re.findall(r'^\s*(\w.+)$',
                                 block.group(1), re.MULTILINE)):
            (snd_id, src) = def_.split()

            if 'nosound' in global_vars.cmd: # For faster compiles
                self.consist.add_switch(
                    name=snd_id, var='param_volume',
                    range_vals=[('default', 'CB_FAILED')])
                continue

            switch_name = 'snd_{}'.format(n)
            range_vals = {}
            for v in [100, 66, 33]:
                self.consist.add_switch(
                    name=switch_name + '_depot_{}'.format(v),
                    var='vehicle_is_in_depot',
                    range_vals=[
                        (0, 'sound("src/sfx/{}_idle.wav", {})'.format(src,
                                                                      v))])
                self.consist.add_switch(
                    name=switch_name + '_{}'.format(v),
                    var='extra_callback_info1',
                    range_vals=[
                        (1, 'sound("src/sfx/{}_acc.wav", {})'.format(src, v)),
                        (7, 'sound("src/sfx/{}_run.wav", {})'.format(src, v)),
                        (8, switch_name + '_depot_{}'.format(v))])

            self.consist.add_switch(
                name=snd_id, var='param_volume',
                range_vals=[
                    (0, 'CB_FAILED'), (1, switch_name + '_33'),
                    (2, switch_name + '_66'), (3, switch_name + '_100')])

    def parse_sprites(self):
        """Parse SPRITES block."""
        block = re.search(r'\bSPRITES\s*\("(.+?)"\)\s*\{(.+?)\}',
                          self.pygfx, re.DOTALL)
        if not block:
            return
        xcf2png_flags = "--background '#FFFFFF' --autocrop --dissolve --output"
        make_rule_stub = 'render: {target}\n{target}: {deps}\n\t{recipe}\n'
        xcf_name = os.path.join(xcfdir, block.group(1))
        xinfo = Xcfinfo(xcf_name)
        spritedefs = re.findall(r'^\s*(\w.+)$', block.group(2), re.MULTILINE)

        for n, spritedef in enumerate(spritedefs):
            layers = ['bg'] # Set bg to bottom
            spl = spritedef.split()
            sprite_id = spl[0]
            layers.extend(spl[1:-1])
            type_ = spl[-1]
            spriteset_name = 'spriteset_{}'.format(n)
            png_name = os.path.join(pngdir, '{}_{}.png'.format(
                self.consist.id_, n))
            self.consist.add_spriteset(name=sprite_id, png=png_name,
                                       type_=type_)
            """
            # If hacked articulated, depot offsets are different
            if self.consist.is_hacked_arti and "pur" not in type_:
                self.consist.add_spriteset(name=sprite_id + "_DEPOT",
                                           png=png_name, type_=type_,
                                           depot=True)
            """
            # Append purchase menu articulated 'grill' layer on top
            # if visible (is this WTF? Should it always be appended?)
            if not type_.isdigit() and xinfo['purchase_grill']['visible']:
                layers.append('purchase_grill') 

            if type_.isdigit() and not 'nohl' in global_vars.cmd:
                # Test if xcf file has headlight layers (HL_)
                # Might also be very wtf. Maybe just manually append
                # headlight layer(s) in SPRITES block if needed
                for lx in xinfo.layernames:
                    if lx == 'HL':
                        layers.append(lx) # No pattern, goes with all layers
                        break
                    if lx.startswith('HL_'): # Got pattern
                        # Check if pattern matches any of the layers
                        # in this def 
                        for l in layers[:]: 
                            if (re.search(lx[3:], l)):
                                layers.append(lx)
                                break # Just in case, if some other
                                      # layer would match too

            # Generate arguments suitable for xcf2png
            xcf2png_str = "@{} {} '{}' '{}' {}".format(
                'xcf2png', xcf2png_flags, png_name, xcf_name,
                ' '.join(['"{}"'.format(l) for l in layers]))
            deps = [xcf_name, __file__]
            self.db.make_list.append(
                make_rule_stub.format(target=png_name, deps=' '.join(deps),
                                      recipe=xcf2png_str))

    def parse_subtype_by_year(self):
        """Parse SUBTYPE_BY_YEAR block."""
        block = re.search(r'SUBTYPE_BY_YEAR\s*\{(.+?)\}', self.pygfx,
                          re.DOTALL)
        if not block:
            return
        yeardefs = re.findall(r'^\s*(\w.+)$', block.group(1), re.MULTILINE)
        subtext_per_year = {}
        for ydef in yeardefs:
            spl = ydef.split(':')
            range_ = spl[0].strip()
            year = re.search(r'\d+', spl[1].strip()).group()
            expression = spl[2].strip()
            try:
                subtext_per_year[year][range_] = expression
            except KeyError:
                subtext_per_year[year] = {range_: expression}
        yearcheck_lines = {}
        subtext_lines = {}
        years = sorted(subtext_per_year)
        # A subtype text switch has to be generated for each unique year
        for n, y in enumerate(years):
            switch_name = 'subtext_{}'.format(y)
            for range_, expression in subtext_per_year[y].items():
                subtext_lines[range_] = expression
            self.consist.add_switch(
                name=switch_name, var='cargo_subtype',
                range_vals=[(k, v) for k, v in subtext_lines.items()])
            try:
                ystr = '{}..{}'.format(y, int(years[n+1]) - 1)
            except IndexError:
                ystr = 'default'
            yearcheck_lines[ystr] = switch_name
        self.consist.add_switch(
            name='SUBTYPE_BY_YEAR', var='current_year',
                range_vals=[(k, v) for k, v in yearcheck_lines.items()])

    def parse_parts(self):
        """Parse PART blocks."""
        parts = re.findall(r'\bPART\s*\{(.*?)\}', self.pygfx, re.DOTALL)
        for p in parts:
            cb_map = {}
            cb_rvals = [cb.strip() for cb in p.split(';')[:-1]]
            for cbr in cb_rvals:
                spl = cbr.split(':')
                cb_map[spl[0].strip()] = spl[1].strip()
            self.consist.add_part(cb_map)

    def parse_nml(self):
        """Parse regular NML."""
        ### Spritegroups ###
        groups = re.findall(
            r'\bspritegroup\s*(\w+)\s*\{(.*?)\}', self.pygfx, re.DOTALL)
        for g in groups:
            name = g[0]
            cont = [c.strip() for c in g[1].split(';')[:-1]]
            assert(len(cont) == 2)
            for line in cont:
                spl = line.split(':')
                typ = spl[0].strip()
                spritesets = re.sub(r'[\[\]]', '', spl[1])
                spritesets = [s.strip() for s in spritesets.split(',')]
                if typ == 'loading':
                    loading = spritesets
                else:
                    loaded = spritesets
            self.consist.add_spritegroup(
                name=name, loading=loading, loaded=loaded)

        ### Switches ###
        switches = re.findall(
            r'\b((?:random_)?switch)\s*\((.*?)\)\s*\{(.*?)\}',
            self.pygfx, re.DOTALL)
        for s in switches:
            title = [xpr.strip() for xpr in s[1].split(',')]
            name = title[2]
            var = title[3] if s[0] == 'switch' else ''
            range_vals = []
            for cbv in [cb.strip() for cb in s[2].strip().split(';')[:-1]]:
                spl = cbv.split(':')
                if len(spl) == 1:
                    range_ = 'default'
                    rval = spl[0].strip()
                else:
                    range_ = spl[0].strip()
                    rval = spl[1].strip()
                range_vals.append((range_, rval))
            self.consist.add_switch(name=name, var=var, range_vals=range_vals)

def parse(db):
    makerules = []
    for dirs in os.walk(os.path.join(cur_dir, 'pygfx')):
        for pygfx_file in [p for p in dirs[2] if p.endswith('.cons')]:
            id_ = os.path.splitext(pygfx_file)[0]
            pygfx_full = os.path.join(dirs[0], pygfx_file)
            with open(pygfx_full) as f:
                p = Parser(db=db, id_=id_, pygfx=f.read())
                makerules.append(p.make_rules)
