from __future__ import print_function, absolute_import, division

print('receiver start')
import sys
import time

import socket
import traceback

from path import Path

from rgbmatrix import RGBMatrix, RGBMatrixOptions
from rgbmatrix import graphics
from PIL import Image
from sender import decode

def asc_desc_modulation(x, interval, clamp=0.1):
    sub_idx = x % interval
    if sub_idx < interval / 2:
        f = (x % interval) / interval
    else:
        f = (interval - x % interval) / interval
    f = max(clamp, f)
    modulate = lambda c: (int(c[0] * f), int(c[1] * f), int(c[2] * f))
    return modulate

def Color(colorstring):
    """ convert #RRGGBB to an (R, G, B) tuple """
    colorstring = colorstring.strip()
    if colorstring[0] == '#': colorstring = colorstring[1:]
    if len(colorstring) != 6:
        raise ValueError, "input #%s is not in #RRGGBB format" % colorstring
    r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
    r, g, b = [int(n, 16) for n in (r, g, b)]
    return (r, g, b)

class Renderer(object):
    def __init__(self, textColor=(255, 255, 255), brightness=255):
        print('initializing renderer')
        self._color = textColor
        self.setup()

        self._brightness = brightness
        self.set_brightness(brightness)

    def set_brightness(self, value):
        self._brightness = value
        self.matrix.brightness = value

    def setup(self):
        o = RGBMatrixOptions()
        o.chain_length = 4
        self.matrix = RGBMatrix(options=o)
        self.canvas = self.matrix.CreateFrameCanvas()
        self.color = graphics.Color(*self._color)

    def destroy(self):
        del self.canvas
        del self.matrix
        del self.color

    def swap(self):
        self.canvas = self.matrix.SwapOnVSync(self.canvas)
        self.canvas.Clear()

    def progress(self, step=0, total=0):
        c = self.canvas
        w, h = c.width, c.height
        col = Color('#AACCFF')
        if total > 0:
            padding = ' ' * (len(str(total-1)) - len(str(step+1)))
            s_len = graphics.DrawText(c, fonts['10x20'], 0, 20, self.color, '%s%s' % (padding, step+1))
            w = w - s_len

            stepsize = max(1, int(w / total))
            for x in range(w):
                modulate = asc_desc_modulation(x, stepsize)
                for y in range(h):
                    c.SetPixel(s_len + x, y, *modulate(col))
                if  x > ((step + 1) / total) * w:
                    break
        self.swap()

    def print_text(self, text):
        graphics.DrawText(self.canvas, fonts['10x20'], 0, 20, self.color, text)
        self.swap()

    def scroll_text(self, text, loop=1):
        pos = self.canvas.width
        i = 1
        while True:
            len = graphics.DrawText(self.canvas, fonts['10x20'], pos, 20, self.color, text)
            pos -= 1
            if (pos + len < 0):
                pos = self.canvas.width
                i += 1
            if i > loop:
                break

            time.sleep(0.02)
            self.swap()


    def fit_text(self, text, width=None, fade=None):
        if width is None:
            width = self.canvas.width

        ff = reversed(sorted(fonts.items(), key=lambda e: int(e[0].split('x')[0])))
        temp_canvas = self.matrix.CreateFrameCanvas()
        for name, font in ff:
            w = graphics.DrawText(temp_canvas, font, 0, -100, self.color, text)
            if w < width:
                break
        else:
            print('str len %s exceeds space' % w)

        if fade:
            t = time.time()
            while time.time() < t + fade:
                x = 1 - ((time.time() - t) / fade)
                #~ self.matrix.brightness = self._brightness * x
                c = graphics.Color(int(self.color.red * x), int(self.color.green * x), int(self.color.blue * x))
                graphics.DrawText(self.canvas, font, 0, 20, c, text)
                self.swap()
                time.sleep(0.02)

        else:
            graphics.DrawText(self.canvas, font, 0, 20, self.color, text)
            self.swap()


try:
    fonts = {}
    fo = graphics.Font()
    fo.LoadFont('/matrix/fonts/10x20.bdf')
    fonts['10x20'] = fo

    r = Renderer(textColor=(96, 128, 255))
    print('loading fonts')
    files = Path('/matrix/fonts').files('*.bdf')

    for i, f in enumerate(files):
        #~ if i % 3 != 0:
            #~ continue
        r.progress(i, len(files))
        if not f.namebase.split('x')[0].isdigit() or not f.namebase[-1].isdigit():
            continue
        fo = graphics.Font()
        fo.LoadFont(f)
        fonts[f.namebase] = fo
    r.progress(0, 0)

    # Create a TCP/IP socket
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    # Bind the socket to the port
    server_address = ('0.0.0.0', 10000)
    print('starting up on %s port %s' % server_address)
    sock.bind(server_address)
    r.fit_text('Matrix ready.', fade=4)

    while True:
        try:
            print('waiting to receive message')
            data, address = sock.recvfrom(4096)

            api_call = decode(data)

            # get name of namedtuple
            method = type(api_call).__name__
            print('received %s bytes from %s [method: %s]' % (len(data), address, method))

            r.set_brightness(255)

            if method == 'destroy':
                # kill SwapOnVSync
                r.destroy()
                # XXX: how to re-setup
                #~ r.setup()
            else:
                kwargs = api_call._asdict()
                getattr(r, method)(**kwargs)

        except Exception:
            print(traceback.format_exc())


except Exception:
    print(traceback.format_exc())

finally:
    print('receiver exit')
