#!/usr/bin/env python


__license__   = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'

from functools import partial
from struct import unpack_from

from calibre.utils.fonts.sfnt import FixedProperty, UnknownTable
from calibre.utils.fonts.sfnt.common import ExtensionSubstitution, FeatureListTable, LookupTable, ScriptListTable, SimpleListTable, UnknownLookupSubTable
from calibre.utils.fonts.sfnt.errors import UnsupportedFont


class SingleSubstitution(UnknownLookupSubTable):

    formats = {1, 2}

    def initialize(self, data):
        if self.format == 1:
            self.delta = data.unpack('h')
        else:
            count = data.unpack('H')
            self.substitutes = data.unpack(f'{count}H', single_special=False)

    def all_substitutions(self, glyph_ids):
        gid_index_map = self.coverage.coverage_indices(glyph_ids)
        if self.format == 1:
            return {gid + self.delta for gid in gid_index_map}
        return {self.substitutes[i] for i in gid_index_map.values()}


class MultipleSubstitution(UnknownLookupSubTable):

    formats = {1}

    def initialize(self, data):
        self.coverage_to_subs_map = self.read_sets(data, set_is_index=True)

    def all_substitutions(self, glyph_ids):
        gid_index_map = self.coverage.coverage_indices(glyph_ids)
        ans = set()
        for index in gid_index_map.values():
            glyphs = set(self.coverage_to_subs_map[index])
            ans |= glyphs
        return ans


class AlternateSubstitution(MultipleSubstitution):
    pass


class LigatureSubstitution(UnknownLookupSubTable):

    formats = {1}

    def initialize(self, data):
        self.coverage_to_lig_map = self.read_sets(data, self.read_ligature)

    def read_ligature(self, data):
        lig_glyph, count = data.unpack('HH')
        components = data.unpack(f'{count - 1}H', single_special=False)
        return lig_glyph, components

    def all_substitutions(self, glyph_ids):
        gid_index_map = self.coverage.coverage_indices(glyph_ids)
        ans = set()
        for start_glyph_id, index in gid_index_map.items():
            for glyph_id, components in self.coverage_to_lig_map[index]:
                components = (start_glyph_id,) + components
                if set(components).issubset(glyph_ids):
                    ans.add(glyph_id)
        return ans


class ContexttualSubstitution(UnknownLookupSubTable):

    formats = {1, 2, 3}

    @property
    def has_initial_coverage(self):
        return self.format != 3

    def initialize(self, data):
        pass  # TODO

    def all_substitutions(self, glyph_ids):
        # This table only defined substitution in terms of other tables
        return set()


class ChainingContextualSubstitution(UnknownLookupSubTable):

    formats = {1, 2, 3}

    @property
    def has_initial_coverage(self):
        return self.format != 3

    def initialize(self, data):
        pass  # TODO

    def all_substitutions(self, glyph_ids):
        # This table only defined substitution in terms of other tables
        return set()


class ReverseChainSingleSubstitution(UnknownLookupSubTable):

    formats = {1}

    def initialize(self, data):
        backtrack_count = data.unpack('H')
        backtrack_offsets = data.unpack(f'{backtrack_count}H',
                single_special=False)
        lookahead_count = data.unpack('H')
        lookahead_offsets = data.unpack(f'{lookahead_count}H',
                single_special=False)
        backtrack_offsets = [data.start_pos + x for x in backtrack_offsets]
        lookahead_offsets = [data.start_pos + x for x in lookahead_offsets]
        backtrack_offsets, lookahead_offsets  # TODO: Use these
        count = data.unpack('H')
        self.substitutes = data.unpack(f'{count}H')

    def all_substitutions(self, glyph_ids):
        gid_index_map = self.coverage.coverage_indices(glyph_ids)
        return {self.substitutes[i] for i in gid_index_map.values()}


subtable_map = {
        1: SingleSubstitution,
        2: MultipleSubstitution,
        3: AlternateSubstitution,
        4: LigatureSubstitution,
        5: ContexttualSubstitution,
        6: ChainingContextualSubstitution,
        8: ReverseChainSingleSubstitution,
}


class GSUBLookupTable(LookupTable):

    def set_child_class(self):
        if self.lookup_type == 7:
            self.child_class = partial(ExtensionSubstitution,
                    subtable_map=subtable_map)
        else:
            self.child_class = subtable_map[self.lookup_type]


class LookupListTable(SimpleListTable):

    child_class = GSUBLookupTable


class GSUBTable(UnknownTable):

    version = FixedProperty('_version')

    def decompile(self):
        (self._version, self.scriptlist_offset, self.featurelist_offset,
                self.lookuplist_offset) = unpack_from(b'>L3H', self.raw)
        if self._version != 0x10000:
            raise UnsupportedFont(f'The GSUB table has unknown version: 0x{self._version:x}')

        self.script_list_table = ScriptListTable(self.raw,
                self.scriptlist_offset)
        # self.script_list_table.dump()

        self.feature_list_table = FeatureListTable(self.raw,
                self.featurelist_offset)
        # self.feature_list_table.dump()

        self.lookup_list_table = LookupListTable(self.raw,
                self.lookuplist_offset)

    def all_substitutions(self, glyph_ids):
        glyph_ids = frozenset(glyph_ids)
        ans = set(glyph_ids)
        for lookup_table in self.lookup_list_table:
            for subtable in lookup_table:
                glyphs = subtable.all_substitutions(ans)
                if glyphs:
                    ans |= glyphs
        return ans - {glyph_ids}
