#!/usr/bin/env python


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

from collections import OrderedDict
from struct import pack

from calibre.utils.fonts.sfnt.cff.constants import cff_standard_strings


class Index(list):

    def __init__(self):
        list.__init__(self)
        self.raw = None

    def calcsize(self, largest_offset):
        if largest_offset < 0x100:
            return 1
        elif largest_offset < 0x10000:
            return 2
        elif largest_offset < 0x1000000:
            return 3
        return 4

    def compile(self):
        if len(self) == 0:
            self.raw = pack(b'>H', 0)
        else:
            offsets = [1]
            for i, obj in enumerate(self):
                offsets.append(offsets[-1] + len(obj))
            offsize = self.calcsize(offsets[-1])
            obj_data = b''.join(self)
            prefix = pack(b'>HB', len(self), offsize)

            if offsize == 3:
                offsets = b''.join(pack(b'>L', x)[1:] for x in offsets)
            else:
                fmt = {1:'B', 2:'H', 4:'L'}[offsize]
                offsets = pack(f'>{len(offsets)}{fmt}'.encode('ascii'),
                        *offsets)

            self.raw = prefix + offsets + obj_data
        return self.raw


class Strings(Index):

    def __init__(self):
        Index.__init__(self)
        self.added = {x:i for i, x in enumerate(cff_standard_strings)}

    def __call__(self, x):
        ans = self.added.get(x, None)
        if ans is None:
            ans = len(self) + len(cff_standard_strings)
            self.added[x] = ans
            self.append(x)
        return ans


class Dict(Index):

    def __init__(self, src, strings):
        Index.__init__(self)
        self.src, self.strings = src, strings

    def compile(self):
        self[:] = [self.src.compile(self.strings)]
        Index.compile(self)


class PrivateDict:

    def __init__(self, src, subrs, strings):
        self.src, self.strings = src, strings
        self.subrs = None
        if subrs is not None:
            self.subrs = Index()
            self.subrs.extend(subrs)
            self.subrs.compile()

    def compile(self):
        raw = self.src.compile(self.strings)
        if self.subrs is not None:
            self.src['Subrs'] = len(raw)
            raw = self.src.compile(self.strings)
        self.raw = raw
        return raw


class Charsets(list):

    def __init__(self, strings):
        list.__init__(self)
        self.strings = strings

    def compile(self):
        ans = pack(b'>B', 0)
        sids = [self.strings(x) for x in self]
        ans += pack(f'>{len(self)}H'.encode('ascii'), *sids)
        self.raw = ans
        return ans


class Subset:

    def __init__(self, cff, keep_charnames):
        self.cff = cff
        keep_charnames.add(b'.notdef')

        header = pack(b'>4B', 1, 0, 4, cff.offset_size)

        # Font names Index
        font_names = Index()
        font_names.extend(self.cff.font_names)

        # Strings Index
        strings = Strings()

        # CharStrings Index and charsets
        char_strings = Index()
        self.charname_map = OrderedDict()
        charsets = Charsets(strings)
        charsets.extend(cff.charset[1:])  # .notdef is not included

        endchar_operator = bytes(bytearray([14]))
        for i in range(self.cff.num_glyphs):
            cname = self.cff.charset.safe_lookup(i)
            ok = cname in keep_charnames
            cs = self.cff.char_strings[i] if ok else endchar_operator
            char_strings.append(cs)
            if ok:
                self.charname_map[cname] = i

        # Add the strings
        char_strings.compile()
        charsets.compile()

        # Global subroutines
        global_subrs = Index()
        global_subrs.extend(cff.global_subrs)
        global_subrs.compile()

        # TOP DICT
        top_dict = Dict(cff.top_dict, strings)
        top_dict.compile()  # Add strings

        private_dict = None
        if cff.private_dict is not None:
            private_dict = PrivateDict(cff.private_dict, cff.private_subrs,
                    strings)
            private_dict.compile()  # Add strings

        fixed_prefix = header + font_names.compile()

        t = top_dict.src
        # Put in dummy offsets
        t['charset'] = 1
        t['CharStrings'] = 1
        if private_dict is not None:
            t['Private'] = (len(private_dict.raw), 1)
        top_dict.compile()

        strings.compile()

        # Calculate real offsets
        pos = len(fixed_prefix)
        pos += len(top_dict.raw)
        pos += len(strings.raw)
        pos += len(global_subrs.raw)
        t['charset'] = pos
        pos += len(charsets.raw)
        t['CharStrings'] = pos
        pos += len(char_strings.raw)
        if private_dict is not None:
            t['Private'] = (len(private_dict.raw), pos)
        top_dict.compile()

        self.raw = (fixed_prefix + top_dict.raw + strings.raw +
                global_subrs.raw + charsets.raw + char_strings.raw)
        if private_dict is not None:
            self.raw += private_dict.raw
            if private_dict.subrs is not None:
                self.raw += private_dict.subrs.raw
