# -*- coding: utf-8 -*-

"""font abstraction"""


# imports ####################################################################

from itertools import chain

from ctypes import byref, string_at

from .utils import get_font

from . import freetype2 as _ft2
_FT = _ft2.FT
_library = _ft2._library


# globals ####################################################################

def _on(tag):
    return bool(tag & 1)


def _cubic(tag):
    return bool(tag & 2)


# face #######################################################################

class Face(object):
    def __init__(self, font_families, font_weight, font_style, px=10):
        font_name, index = get_font(font_families, font_weight, font_style)
        self.face = _ft2.Face()
        self.pen = _ft2.Vector()
        print('_library = %s' % str(_library))
        print('font_name = %s' % str(font_name))
        print('font_name.encode() = %s' % str(font_name.encode()))
        if _FT.New_Face(_library, font_name.encode(), index, byref(self.face)) != 0:
            raise ValueError("unable to create '%s' face" % font_name)
        _FT.Select_Charmap(self.face, _ft2.ENCODING_UNICODE)
        if px is not None:
            self.set_size(px)
        self._FT = _FT  # keep a ref for finalizer
    
    def __del__(self):
        try:
            self._FT.Done_Face(self.face)
        except AttributeError:
            pass
    
    def set_size(self, px):
        _FT.Set_Pixel_Sizes(self.face, 0, px)
    
    def set_transform(self, a=1., b=0., c=0., d=1., e=0., f=0.):
        matrix = _ft2.Matrix()
        matrix.xx, matrix.xy = int(a * 0x10000), int(b * 0x10000)
        matrix.yx, matrix.yy = int(c * 0x10000), int(d * 0x10000)
        self.pen.x = int(e * 64)
        self.pen.y = int(-f * 64)
        _FT.Set_Transform(self.face, byref(matrix), byref(self.pen))
    
    def _glyph(self, uc):
        glyph_index = _FT.Get_Char_Index(self.face, ord(uc))
        _FT.Load_Glyph(self.face, glyph_index, _ft2.LOAD_DEFAULT)
        return self.face.contents.glyph.contents
    
    def get_hkerning(self, ucl, ucr):
        if ucl is None:
            return 0.
        
        left_glyph = _FT.Get_Char_Index(self.face, ord(ucl))
        right_glyph = _FT.Get_Char_Index(self.face, ord(ucr))
        kerning = _ft2.Vector()
        _FT.Get_Kerning(self.face, left_glyph, right_glyph,
                        _ft2.KERNING_DEFAULT, byref(kerning))
        return kerning.x / 64.
    
    def get_bbox(self, text):
        width = 0
        top, bottom = 0, 0
        up = None
        glyph = None
        for uc in text:
            width += self.get_hkerning(up, uc)
            up = uc
            
            glyph = self._glyph(uc)
            width += glyph.metrics.horiAdvance / 64.
            top = max(top, glyph.metrics.horiBearingY / 64.)
            bottom = min(bottom, (glyph.metrics.horiBearingY -
                                  glyph.metrics.height) / 64.)
        if glyph:
            width += (glyph.metrics.horiBearingX + glyph.metrics.width -
                      glyph.metrics.horiAdvance) / 64.
        return (0., -top), (width, top - bottom)
    
    def bitmap(self, uc):
        glyph = self._glyph(uc)
        _FT.Render_Glyph(byref(glyph), _ft2.RENDER_MODE_NORMAL)
        
        origin = glyph.bitmap_left, -glyph.bitmap_top
        bitmap = glyph.bitmap
        assert bitmap.pixel_mode == _ft2.PIXEL_MODE_GRAY, bitmap.pixel_mode
        
        rows, columns = bitmap.rows, bitmap.pitch
        size = columns, rows
        offset = glyph.advance.x / 64., -glyph.advance.y / 64.
        data = string_at(bitmap.buffer, rows * columns)
        data = bytes(chain(*([255, 255, 255, c] for c in data)))
        return origin, size, offset, data
    
    def outline(self, uc):
        glyph = self._glyph(uc)

        outline = glyph.outline
        
        data = []
        b = 0
        for c in range(outline.n_contours):
            e = outline.contours[c]
            
            # ensure we start with an 'on' point
            for s in range(b, e + 1):
                if _on(outline.tags[s]):
                    break
            
            # generate path data
            contour = []
            command, offs = 'M', []
            for i in chain(range(s, e + 1), range(b, s + 1)):
                point, tag = outline.points[i], outline.tags[i]
                point = (point.x / 64., -point.y / 64.)
                if _on(tag):  # 'on' point
                    contour.append(command)
                    if command is 'Q' and len(offs) >= 2:
                        (x0, y0) = offs[0]
                        for (x1, y1) in offs[1:]:
                            contour += [(x0, y0), ((x0 + x1) / 2, (y0 + y1) / 2), 'Q']
                            x0, y0 = x1, y1
                        contour.append((x0, y0))
                    else:  # 'off' point
                        contour += offs
                    contour.append(point)
                    command, offs = 'L', []
                else:
                    offs.append(point)
                    command = 'C' if _cubic(tag) else 'Q'
            if contour:
                contour.append('Z')
            data += contour
            b = e + 1
        
        # bbox
        bbox = _ft2.BBox()
        _FT.Outline_Get_BBox(byref(outline), byref(bbox))
        xmin, xmax = bbox.xMin / 64., bbox.xMax / 64.
        ymin, ymax = bbox.yMin / 64., bbox.yMax / 64.
        
        origin = xmin, -ymax
        size = xmax - xmin, ymax - ymin
        offset = glyph.advance.x / 64., -glyph.advance.y / 64.
        return origin, size, offset, data
