from fontTools.voltLib.error import VoltLibError
from typing import NamedTuple


class Pos(NamedTuple):
    adv: int
    dx: int
    dy: int
    adv_adjust_by: dict
    dx_adjust_by: dict
    dy_adjust_by: dict

    def __str__(self):
        res = ' POS'
        for attr in ('adv', 'dx', 'dy'):
            value = getattr(self, attr)
            if value is not None:
                res += f' {attr.upper()} {value}'
                adjust_by = getattr(self, f'{attr}_adjust_by', {})
                for size, adjustment in adjust_by.items():
                    res += f' ADJUST_BY {adjustment} AT {size}'
        res += ' END_POS'
        return res


class Element(object):
    def __init__(self, location=None):
        self.location = location

    def build(self, builder):
        pass

    def __str__(self):
        raise NotImplementedError


class Statement(Element):
    pass


class Expression(Element):
    pass


class VoltFile(Statement):
    def __init__(self):
        Statement.__init__(self, location=None)
        self.statements = []

    def build(self, builder):
        for s in self.statements:
            s.build(builder)

    def __str__(self):
        return '\n' + '\n'.join(str(s) for s in self.statements) + ' END\n'


class GlyphDefinition(Statement):
    def __init__(self, name, gid, gunicode, gtype, components, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.id = gid
        self.unicode = gunicode
        self.type = gtype
        self.components = components

    def __str__(self):
        res = f'DEF_GLYPH "{self.name}" ID {self.id}'
        if self.unicode is not None:
            if len(self.unicode) > 1:
                unicodes = ','.join(f'U+{u:04X}' for u in self.unicode)
                res += f' UNICODEVALUES "{unicodes}"'
            else:
                res += f' UNICODE {self.unicode[0]}'
        if self.type is not None:
            res += f' TYPE {self.type}'
        if self.components is not None:
            res += f' COMPONENTS {self.components}'
        res += ' END_GLYPH'
        return res


class GroupDefinition(Statement):
    def __init__(self, name, enum, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.enum = enum
        self.glyphs_ = None

    def glyphSet(self, groups=None):
        if groups is not None and self.name in groups:
            raise VoltLibError(
                'Group "%s" contains itself.' % (self.name),
                self.location)
        if self.glyphs_ is None:
            if groups is None:
                groups = set({self.name})
            else:
                groups.add(self.name)
            self.glyphs_ = self.enum.glyphSet(groups)
        return self.glyphs_

    def __str__(self):
        enum = self.enum and str(self.enum) or ''
        return f'DEF_GROUP "{self.name}"\n{enum}\nEND_GROUP'


class GlyphName(Expression):
    """A single glyph name, such as cedilla."""
    def __init__(self, glyph, location=None):
        Expression.__init__(self, location)
        self.glyph = glyph

    def glyphSet(self):
        return (self.glyph,)

    def __str__(self):
        return f' GLYPH "{self.glyph}"'


class Enum(Expression):
    """An enum"""
    def __init__(self, enum, location=None):
        Expression.__init__(self, location)
        self.enum = enum

    def __iter__(self):
        for e in self.glyphSet():
            yield e

    def glyphSet(self, groups=None):
        glyphs = []
        for element in self.enum:
            if isinstance(element, (GroupName, Enum)):
                glyphs.extend(element.glyphSet(groups))
            else:
                glyphs.extend(element.glyphSet())
        return tuple(glyphs)

    def __str__(self):
        enum = ''.join(str(e) for e in self.enum)
        return f' ENUM{enum} END_ENUM'


class GroupName(Expression):
    """A glyph group"""
    def __init__(self, group, parser, location=None):
        Expression.__init__(self, location)
        self.group = group
        self.parser_ = parser

    def glyphSet(self, groups=None):
        group = self.parser_.resolve_group(self.group)
        if group is not None:
            self.glyphs_ = group.glyphSet(groups)
            return self.glyphs_
        else:
            raise VoltLibError(
                'Group "%s" is used but undefined.' % (self.group),
                self.location)

    def __str__(self):
        return f' GROUP "{self.group}"'


class Range(Expression):
    """A glyph range"""
    def __init__(self, start, end, parser, location=None):
        Expression.__init__(self, location)
        self.start = start
        self.end = end
        self.parser = parser

    def glyphSet(self):
        return tuple(self.parser.glyph_range(self.start, self.end))

    def __str__(self):
        return f' RANGE "{self.start}" TO "{self.end}"'


class ScriptDefinition(Statement):
    def __init__(self, name, tag, langs, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.tag = tag
        self.langs = langs

    def __str__(self):
        res = 'DEF_SCRIPT'
        if self.name is not None:
            res += f' NAME "{self.name}"'
        res += f' TAG "{self.tag}"\n\n'
        for lang in self.langs:
            res += f'{lang}'
        res += 'END_SCRIPT'
        return res


class LangSysDefinition(Statement):
    def __init__(self, name, tag, features, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.tag = tag
        self.features = features

    def __str__(self):
        res = 'DEF_LANGSYS'
        if self.name is not None:
            res += f' NAME "{self.name}"'
        res += f' TAG "{self.tag}"\n\n'
        for feature in self.features:
            res += f'{feature}'
        res += 'END_LANGSYS\n'
        return res


class FeatureDefinition(Statement):
    def __init__(self, name, tag, lookups, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.tag = tag
        self.lookups = lookups

    def __str__(self):
        res = f'DEF_FEATURE NAME "{self.name}" TAG "{self.tag}"\n'
        res += ' ' + ' '.join(f'LOOKUP "{l}"' for l in self.lookups) + '\n'
        res += 'END_FEATURE\n'
        return res


class LookupDefinition(Statement):
    def __init__(self, name, process_base, process_marks, mark_glyph_set,
                 direction, reversal, comments, context, sub, pos,
                 location=None):
        Statement.__init__(self, location)
        self.name = name
        self.process_base = process_base
        self.process_marks = process_marks
        self.mark_glyph_set = mark_glyph_set
        self.direction = direction
        self.reversal = reversal
        self.comments = comments
        self.context = context
        self.sub = sub
        self.pos = pos

    def __str__(self):
        res = f'DEF_LOOKUP "{self.name}"'
        res += f' {self.process_base and "PROCESS_BASE" or "SKIP_BASE"}'
        if self.process_marks:
            res += ' PROCESS_MARKS '
            if self.mark_glyph_set:
                res += f'MARK_GLYPH_SET "{self.mark_glyph_set}"'
            elif isinstance(self.process_marks, str):
                res += f'"{self.process_marks}"'
            else:
                res += 'ALL'
        else:
            res += ' SKIP_MARKS'
        if self.direction is not None:
            res += f' DIRECTION {self.direction}'
        if self.reversal:
            res += ' REVERSAL'
        if self.comments is not None:
            comments = self.comments.replace('\n', r'\n')
            res += f'\nCOMMENTS "{comments}"'
        if self.context:
            res += '\n' + '\n'.join(str(c) for c in self.context)
        else:
            res += '\nIN_CONTEXT\nEND_CONTEXT'
        if self.sub:
            res += f'\n{self.sub}'
        if self.pos:
            res += f'\n{self.pos}'
        return res


class SubstitutionDefinition(Statement):
    def __init__(self, mapping, location=None):
        Statement.__init__(self, location)
        self.mapping = mapping

    def __str__(self):
        res = 'AS_SUBSTITUTION\n'
        for src, dst in self.mapping.items():
            src = ''.join(str(s) for s in src)
            dst = ''.join(str(d) for d in dst)
            res += f'SUB{src}\nWITH{dst}\nEND_SUB\n'
        res += 'END_SUBSTITUTION'
        return res


class SubstitutionSingleDefinition(SubstitutionDefinition):
    pass


class SubstitutionMultipleDefinition(SubstitutionDefinition):
    pass


class SubstitutionLigatureDefinition(SubstitutionDefinition):
    pass


class SubstitutionReverseChainingSingleDefinition(SubstitutionDefinition):
    pass


class PositionAttachDefinition(Statement):
    def __init__(self, coverage, coverage_to, location=None):
        Statement.__init__(self, location)
        self.coverage = coverage
        self.coverage_to = coverage_to

    def __str__(self):
        coverage = ''.join(str(c) for c in self.coverage)
        res = f'AS_POSITION\nATTACH{coverage}\nTO'
        for coverage, anchor in self.coverage_to:
            coverage = ''.join(str(c) for c in coverage)
            res += f'{coverage} AT ANCHOR "{anchor}"'
        res += '\nEND_ATTACH\nEND_POSITION'
        return res


class PositionAttachCursiveDefinition(Statement):
    def __init__(self, coverages_exit, coverages_enter, location=None):
        Statement.__init__(self, location)
        self.coverages_exit = coverages_exit
        self.coverages_enter = coverages_enter

    def __str__(self):
        res = 'AS_POSITION\nATTACH_CURSIVE'
        for coverage in self.coverages_exit:
            coverage = ''.join(str(c) for c in coverage)
            res += f'\nEXIT {coverage}'
        for coverage in self.coverages_enter:
            coverage = ''.join(str(c) for c in coverage)
            res += f'\nENTER {coverage}'
        res += '\nEND_ATTACH\nEND_POSITION'
        return res


class PositionAdjustPairDefinition(Statement):
    def __init__(self, coverages_1, coverages_2, adjust_pair, location=None):
        Statement.__init__(self, location)
        self.coverages_1 = coverages_1
        self.coverages_2 = coverages_2
        self.adjust_pair = adjust_pair

    def __str__(self):
        res = 'AS_POSITION\nADJUST_PAIR\n'
        for coverage in self.coverages_1:
            coverage = ' '.join(str(c) for c in coverage)
            res += f' FIRST {coverage}'
        res += '\n'
        for coverage in self.coverages_2:
            coverage = ' '.join(str(c) for c in coverage)
            res += f' SECOND {coverage}'
        res += '\n'
        for (id_1, id_2), (pos_1, pos_2) in self.adjust_pair.items():
            res += f' {id_1} {id_2} BY{pos_1}{pos_2}\n'
        res += '\nEND_ADJUST\nEND_POSITION'
        return res


class PositionAdjustSingleDefinition(Statement):
    def __init__(self, adjust_single, location=None):
        Statement.__init__(self, location)
        self.adjust_single = adjust_single

    def __str__(self):
        res = 'AS_POSITION\nADJUST_SINGLE'
        for coverage, pos in self.adjust_single:
            coverage = ''.join(str(c) for c in coverage)
            res += f'{coverage} BY{pos}'
        res += '\nEND_ADJUST\nEND_POSITION'
        return res



class ContextDefinition(Statement):
    def __init__(self, ex_or_in, left=None, right=None, location=None):
        Statement.__init__(self, location)
        self.ex_or_in = ex_or_in
        self.left = left if left is not None else []
        self.right = right if right is not None else []

    def __str__(self):
        res = self.ex_or_in + '\n'
        for coverage in self.left:
            coverage = ''.join(str(c) for c in coverage)
            res += f' LEFT{coverage}\n'
        for coverage in self.right:
            coverage = ''.join(str(c) for c in coverage)
            res += f' RIGHT{coverage}\n'
        res += 'END_CONTEXT'
        return res


class AnchorDefinition(Statement):
    def __init__(self, name, gid, glyph_name, component, locked,
                 pos, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.gid = gid
        self.glyph_name = glyph_name
        self.component = component
        self.locked = locked
        self.pos = pos

    def __str__(self):
        locked = self.locked and ' LOCKED' or ''
        return (f'DEF_ANCHOR "{self.name}"'
                f' ON {self.gid}'
                f' GLYPH {self.glyph_name}'
                f' COMPONENT {self.component}'
                f'{locked}'
                f' AT {self.pos} END_ANCHOR')


class SettingDefinition(Statement):
    def __init__(self, name, value, location=None):
        Statement.__init__(self, location)
        self.name = name
        self.value = value

    def __str__(self):
        if self.value is True:
            return f'{self.name}'
        if isinstance(self.value, (tuple, list)):
            value = " ".join(str(v) for v in self.value)
            return f'{self.name} {value}'
        return f'{self.name} {self.value}'
