import json
import math
from pathlib import Path
from cairo import Context, LineCap, LineJoin, PSSurface, SVGSurface, Gradient, LinearGradient
# from scipy.integrate import quad

from island.match import Match


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def move_to(self, newx, newy):
        self.x = newx
        self.y = newy

    def rel_move_to(self, offx, offy):
        self.x += offx
        self.y += offy

    def new_rel_point(self, offx, offy):
        return Point(self.x + offx, self.y + offy)

class Color:
    def __init__(self, r = 0, g = 0, b = 0, a = 0):
        self.r = r
        self.g = g
        self.b = b
        self.a = a

    def shade(self, percentage):
        return Color(self.r, self.g, self.b, 0.8 * percentage + 0.2)

def set_color(ctx, color):
    """
    :param ctx: context
    :param color: the Color
    """
    ctx.set_source_rgba(color.r, color.g, color.b, color.a)

def draw_alive_face(ctx, pt, r, w):
    """
    :param ctx: context
    :param pt: the center point
    :param r: radius
    :param w: line width
    """
    ctx.new_path()
    ctx.set_source_rgba(1, 1, 1, 1)
    ctx.set_line_width(w)
    ctx.arc(pt.x, pt.y, r, 0, 2*math.pi)
    ctx.fill()
    ctx.set_source_rgba(0, 0, 0, 1)
    ctx.arc(pt.x, pt.y, r, 0, 2*math.pi)
    ctx.stroke()
    ctx.arc(pt.x - r / SQRT2 / 2, pt.y - r / SQRT2 / 2, r / 10, 0, 2 * math.pi)
    ctx.fill()
    ctx.arc(pt.x + r / SQRT2 / 2, pt.y - r / SQRT2 / 2, r / 10, 0, 2 * math.pi)
    ctx.fill()
    ctx.set_line_cap(LineCap.ROUND)
    ctx.arc(pt.x, pt.y, r * GOLDEN_SECTION, math.pi / 4, math.pi / 4 * 3)
    ctx.stroke()

    
def draw_dead_face(ctx, pt, r, w):
    """
    :param ctx: context
    :param pt: the center point
    :param r: radius
    :param w: line width
    """
    ctx.set_line_cap(LineCap.ROUND)
    ctx.set_line_join(LineJoin.ROUND)

    ctx.new_path()
    ctx.set_source_rgba(1, 1, 1, 1)
    ctx.set_line_width(w)
    ctx.arc(pt.x, pt.y, r, 0, 2*math.pi)
    ctx.fill()
    ctx.set_source_rgba(0.5, 0.5, 0.5, 1)
    ctx.arc(pt.x, pt.y, r, 0, 2*math.pi)
    ctx.stroke()

    offset = r / 6 * SQRT3 / 2
    ctx.move_to(pt.x - r / SQRT2 / 2 + offset, pt.y - r / SQRT2 / 2 + r / 6)
    ctx.rel_line_to(- r / 6 * SQRT3, - r / 6)
    ctx.move_to(pt.x - r / SQRT2 / 2 + offset, pt.y - r / SQRT2 / 2 + r / 6)
    ctx.rel_line_to(- r / 6 * SQRT3, r / 6)
    ctx.move_to(pt.x + r / SQRT2 / 2 - offset, pt.y - r / SQRT2 / 2 + r / 6)
    ctx.rel_line_to(r / 6 * SQRT3, - r / 6)
    ctx.move_to(pt.x + r / SQRT2 / 2 - offset, pt.y - r / SQRT2 / 2 + r / 6)
    ctx.rel_line_to(r / 6 * SQRT3, r / 6)
    ctx.stroke()

    a = r * GOLDEN_SECTION
    ctx.move_to(pt.x - a / SQRT2, pt.y + a / SQRT2)
    step = a / (4 * SQRT2)
    for _ in range(4):
        ctx.rel_line_to(step, -step)
        ctx.rel_line_to(step, step)
    ctx.stroke()

def bezier_diff(x, cax, cbx, ccx, cay, cby, ccy):
    return math.sqrt((cax + cbx * x + ccx * (x ** 2)) ** 2 + (cay + cby * x + ccy * (x ** 2)) ** 2)

def calc_bezier_diff_angle(p0, p1, p2, p3, al):
    """
    f(x) = a - 3ax + 3ax^2 - ax^3 + 3bx - 6bx^2 + 3bx^3 + 3cx^2 - 3cx^3 + dx^3
    f'(x) = (-3a+3b) + 2(3a - 6b +3c)x + 3(-a+3b-3c+d)x^2
    """
    # cax = -3 * p0.x +  3 * p1.x
    # cbx =  6 * p0.x - 12 * p1.x + 6 * p2.x
    # ccx = -3 * p0.x +  9 * p1.x - 9 * p2.x + 3 * p3.x
    # cay = -3 * p0.y +  3 * p1.y
    # cby =  6 * p0.y - 12 * p1.y + 6 * p2.y
    # ccy = -3 * p0.y +  9 * p1.y - 9 * p2.y + 3 * p3.y
    
    # s = quad(bezier_diff, 0, 1, args=(cax, cbx, ccx, cay, cby, ccy))
    # t = 1 - al / s[0] * 1.1
    # bx = p0.x * (1-t) ** 3 + 3 * p1.x * t * (1-t) ** 2 + 3 * p2.x * t ** 2 * (1-t) + p3.x * t ** 3
    # by = p0.y * (1-t) ** 3 + 3 * p1.y * t * (1-t) ** 2 + 3 * p2.y * t ** 2 * (1-t) + p3.y * t ** 3
    # a = math.asin((by - p3.y) / math.sqrt((bx - p3.x) ** 2 + (by - p3.y) ** 2))
    # if bx < p3.x:
    #     a = math.pi - a
    a = math.pi * 5 / 4 if p0.x < p3.x else math.pi / 4 
    return  a - math.pi / 30

def draw_action_arrow(ctx, cf, ct, w, al, r, c):
    """
    :param ctx: context
    :param cf: from the center point
    :param ct: to the center point
    :param w: line width
    :param al: arrow length
    :param r: radius
    :param c: Color
    """
    ctx.set_source_rgba(c.r, c.g, c.b, c.a)
    ctx.set_line_width(w)
    ctx.set_line_cap(LineCap.ROUND)
    ctx.set_line_join(LineJoin.ROUND)
    ltr = 1 if cf.x < ct.x else -1
    af = Point(cf.x + ltr * r / SQRT2, cf.y - ltr * r / SQRT2)
    at = Point(ct.x - ltr * r / SQRT2, ct.y - ltr * r / SQRT2)
    ctx.move_to(af.x, af.y)
    extend = (ltr * (ct.x - cf.x)) ** 0.7
    p1 = Point(af.x + ltr * extend / SQRT2, af.y - ltr * extend / SQRT2)
    p2 = Point(at.x - ltr * extend / SQRT2, at.y - ltr * extend / SQRT2)
    ctx.curve_to(p1.x, p1.y, p2.x, p2.y, at.x, at.y)
    a = calc_bezier_diff_angle(af, p1, p2, at, al)
    ctx.move_to(at.x, at.y)
    ctx.line_to(at.x + al * math.cos(a - math.pi / 12), at.y + al * math.sin(a - math.pi / 12))
    ctx.move_to(at.x, at.y)
    ctx.line_to(at.x + al * math.cos(a + math.pi / 12), at.y + al * math.sin(a + math.pi / 12))
    ctx.stroke()

def draw_legend_arrow(ctx, cf, ct, w, al, c):
    """
    :param ctx: context
    :param cf: from the center point
    :param ct: to the center point
    :param w: line width
    :param al: arrow length
    :param c: Color
    """
    ctx.set_source_rgba(c.r, c.g, c.b, c.a)
    ctx.set_line_width(w)
    ctx.set_line_cap(LineCap.ROUND)
    ctx.set_line_join(LineJoin.ROUND)
    ctx.move_to(cf.x, cf.y)
    ctx.line_to(ct.x, ct.y)
    ctx.line_to(ct.x - al * math.cos(-math.pi / 12), ct.y + al * math.sin(-math.pi / 12))
    ctx.move_to(ct.x, ct.y)
    ctx.line_to(ct.x - al * math.cos(math.pi / 12), ct.y + al * math.sin(math.pi / 12))
    ctx.stroke()


def draw_food_bar(ctx, refp, food, foodi, w, h):
    """
    :param ctx: context
    :param refp: reference point, left bottom
    :param food: food
    :param foodi: initial food
    :param w: bar width
    :param h: bar height
    """
    ctx.set_line_width(w / 10)
    ctx.set_line_cap(LineCap.SQUARE)
    ctx.set_line_join(LineJoin.MITER)
    if food > 0:
        foodh = food / foodi * h
        ctx.rectangle(refp.x, refp.y - foodh, w, foodh)
        set_color(ctx, TAMAMOROKOSHI)
        ctx.fill()
    ctx.rectangle(refp.x, refp.y - h, w, h)
    ctx.set_source_rgba(0, 0, 0, 1)
    ctx.stroke()

def draw_linear_gradient_rect(ctx, tl, br, lw, start, stop, horizonal=True):
    """
    :param ctx: context
    :param tl: the top-left reference point
    :param br: the right-bottom reference point 
    :param lw: line width
    :param start: start color
    :param stop: stop color
    :param horizonal: if the gradient is horizonal
    """
    ctx.set_line_cap(LineCap.SQUARE)
    ctx.set_line_join(LineJoin.MITER)
    # source = ctx.get_source()
    lr = LinearGradient(tl.x, tl.y, br.x, br.y)
    lr.add_color_stop_rgba(0, start.r, start.g, start.b, start.a)
    lr.add_color_stop_rgba(1, stop.r, stop.g, stop.b, stop.a)
    ctx.set_source(lr)
    ctx.rectangle(tl.x, tl.y, br.x - tl.x, br.y - tl.y)
    ctx.fill()
    # ctx.rectangle(tl.x, tl.y, br.x - tl.x, br.y - tl.y)
    # ctx.set_line_width(lw)
    # ctx.set_source_rgba(0, 0, 0, 1)
    # ctx.stroke()


def draw_faces(ctx, p, f, r, lw):
    """
    :param ctx: context
    :param p: positions
    :param f: foods
    :param r: radius
    :param lw: line width
    """
    for k in p.keys():
        if f[k] > 0:
            draw_alive_face(ctx, p[k], r, lw)
        else:
            draw_dead_face(ctx, p[k], r, lw)

        draw_food_bar(ctx, p[k].new_rel_point(r / SQRT2 + 20, r / SQRT2), f[k], 5, 10, r * SQRT2)
        # draw_text(ctx, str(k), Point(p[k].x, p[k].y - r - 15), Point(p[k].x, p[k].y - r - 5), CENTER_ALIGNED|MIDDLE_ALIGNED, 12)

def draw_text(ctx, text, tl, br, align, font_size=None, color=Color(0,0,0,1), font_face=None, font_matrix=None, font_options=None):
    """
    draw text
    :param ctx: context
    :param text: the text 
    :param tl: the top-left reference point
    :param br: the right-bottom reference point 
    :param align: alignment
    :param font_size: float font size
    :param color: text color
    :param font_face: font face
    :param font_matrix: font matrix
    :param font_options: font options
    """
    if font_size is not None:
        ctx.set_font_size(font_size)
    if font_face is not None:
        ctx.set_font_face(font_face)
    if font_matrix is not None:
        ctx.set_font_matrix(font_matrix)
    if font_options is not None:
        ctx.set_font_options(font_options)

    te = ctx.text_extents(text)
    ha = align & 0b00001111
    if ha == CENTER_ALIGNED:
        x = (br.x + tl.x - te.width) / 2 - te.x_bearing / 2
    elif ha == RIGHT_ALIGNED:
        x = br.x - te.width
    else:# ha == LEFT_ALIGNED
        x = tl.x - te.x_bearing
    va = align & 0b11110000
    if va == MIDDLE_ALIGNED:
        y = (br.y + tl.y + te.height) / 2 - (te.height + te.y_bearing) / 2
    elif va == BOTTOM_ALIGNED:
        y = br.y
    else: #va == TOP_ALIGNED:
        y = tl.y + te.height - (te.height + te.y_bearing)
    ctx.move_to(x, y)
    set_color(ctx, color)
    ctx.show_text(text)

LEFT_ALIGNED   = 0b00000001
CENTER_ALIGNED = 0b00000010
RIGHT_ALIGNED  = 0b00000100
TOP_ALIGNED    = 0b00010000
MIDDLE_ALIGNED = 0b00100000
BOTTOM_ALIGNED = 0b01000000

GOLDEN_SECTION = 0.6180339887
SQRT2          = 1.4142135623
SQRT3          = 1.7320508076

FACE_WIDTH = 100
FACE_INTERVAL = 50
ROUND_HEIGHT = 300
LINEWIDTH = 4
ROUND_NO_WIDTH = 75

TAMAMOROKOSHI = Color(0.9098039215686275, 0.7137254901960784, 0.2784313725490196, 1)
AKE = Color(0.8, 0.3294117647058824, 0.2274509803921569, 1)
TOKIWA = Color(0.1058823529411765, 0.5058823529411765, 0.2431372549019608, 1)

def draw_picture(fin,fout):
    M = Match.read_from_json(fin)
    info = M.query('game', 'created').select('info').first()['info']
    conf = json.loads(info['config'])
    game_end_at = int(info['game_end_at'])
    P = {} # position
    F = {} # face
    R = 30
    
    for r in M.query('player', 'join').raw_data:
        P[r['pid']] = Point(len(F) * FACE_WIDTH + FACE_INTERVAL + ROUND_NO_WIDTH, ROUND_HEIGHT / 2)
        F[r['pid']] = 5

    surface = SVGSurface(fout, (len(P) + 1) * (FACE_WIDTH) + ROUND_NO_WIDTH, (game_end_at + 2) * ROUND_HEIGHT)
    # surface = PSSurface(fout, (len(P)+1)*(FACE_WIDTH) + ROUND_NO_WIDTH, (game_end_at+2)*ROUND_HEIGHT)
    # surface.set_eps(True)
    context = Context(surface)

    for i in range(1, game_end_at + 2):
        next_f = F.copy()
        draw_text(context, "R# %d" % i, Point(5, ROUND_HEIGHT/2+(i-1)*ROUND_HEIGHT), Point(ROUND_NO_WIDTH, ROUND_HEIGHT/2+(i-1)*ROUND_HEIGHT), LEFT_ALIGNED | MIDDLE_ALIGNED, 40)
        for r in M.query('action', 'done').where(lambda row: row['rno'] == i).raw_data:
            draw_action_arrow(context, P[r['a']], P[r['b']], LINEWIDTH, 15, R, AKE.shade(r['tr'] / 1440) if r['act_a'] == 'D' else TOKIWA.shade(r['tr']/1440))
            draw_action_arrow(context, P[r['b']], P[r['a']], LINEWIDTH, 15, R, AKE.shade(r['tr'] / 1440) if r['act_b'] == 'D' else TOKIWA.shade(r['tr']/1440))
            next_f[r['a']] += conf['payoffs']["%s%s"%(r['act_a'], r['act_b'])][0] * r['tr'] / 1440.0
            next_f[r['b']] += conf['payoffs']["%s%s"%(r['act_a'], r['act_b'])][1] * r['tr'] / 1440.0
        draw_faces(context, P, F, R, LINEWIDTH)
        for k in next_f.keys():
            F[k] = next_f[k] - conf['rounds']['consumption']
            P[k].rel_move_to(0, ROUND_HEIGHT)

    # draw legend
    draw_legend_arrow(context, Point(50, ROUND_HEIGHT * (game_end_at + 1)), Point(75, ROUND_HEIGHT * (game_end_at + 1)), LINEWIDTH, 15, TOKIWA)
    draw_legend_arrow(context, Point(50, ROUND_HEIGHT * (game_end_at + 1) + 30), Point(75, ROUND_HEIGHT * (game_end_at + 1) + 30), LINEWIDTH, 15, AKE)
    draw_text(context, 'C', Point(100, ROUND_HEIGHT * (game_end_at + 1)), Point(120, ROUND_HEIGHT * (game_end_at + 1)), LEFT_ALIGNED|TOP_ALIGNED, 30)
    draw_text(context, 'D', Point(100, ROUND_HEIGHT * (game_end_at + 1) + 30), Point(120, ROUND_HEIGHT * (game_end_at + 1) + 30), LEFT_ALIGNED|TOP_ALIGNED, 30)
    draw_linear_gradient_rect(context, Point(200, ROUND_HEIGHT * (game_end_at + 1)), Point(350, ROUND_HEIGHT * (game_end_at + 1) + 20), 2, TOKIWA, TOKIWA.shade(0))
    draw_linear_gradient_rect(context, Point(200, ROUND_HEIGHT * (game_end_at + 1)+30), Point(350, ROUND_HEIGHT * (game_end_at + 1) + 50), 2, AKE, AKE.shade(0))
    
    


    surface.finish()


if __name__ == '__main__':
    for file in Path('wos-data-compete').iterdir():
        p = Path(file)
        if p.suffix == '.json':
            name = p.stem
            draw_picture(str(p), str(p.parent.parent / 'graph' / ("%s.svg"%name)))
    # draw_picture("wos-data-new/G285.json", "graph/G285.eps")
