from __future__ import print_function
import sys
from math import e
from random import randint

from gi.repository import Gtk, GObject
from gi.repository import Gdk

from pychess.System import uistuff
from pychess.System.idle_add import idle_add
from pychess.System.prefix import addDataPrefix
from pychess.Utils.const import WHITE, DRAW, RUNNING, WHITEWON, BLACKWON
from pychess.Utils.lutils import leval

__title__ = _("Score")

__icon__ = addDataPrefix("glade/panel_score.svg")

__desc__ = _("The score panel tries to evaluate the positions and shows you a graph of the game progress")

class Sidepanel:

    def load (self, gmwidg):
        self.boardview = gmwidg.board.view
        self.plot = ScorePlot(self.boardview)
        self.sw = __widget__ = Gtk.ScrolledWindow()
        __widget__.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        port = Gtk.Viewport()
        port.add(self.plot)
        port.set_shadow_type(Gtk.ShadowType.NONE)
        __widget__.add(port)
        __widget__.show_all()

        self.plot.connect("selected", self.plot_selected)
        self.boardview.connect('shownChanged', self.shownChanged)
        self.boardview.model.connect_after("game_changed", self.game_changed)
        self.boardview.model.connect_after("moves_undone", self.moves_undone)
        self.boardview.model.connect_after("analysis_changed", self.analysis_changed)
        self.boardview.model.connect_after("game_started", self.game_started)

        uistuff.keepDown(__widget__)

        return __widget__

    def moves_undone (self, model, moves):
        for i in range(moves):
            self.plot.undo()

        # As shownChanged will normally be emitted just after game_changed -
        # if we are viewing the latest position - we can do the selection change
        # now, and thereby avoid redraw being called twice
        if self.plot.selected == model.ply-model.lowply:
            self.plot.select(model.ply-model.lowply - moves)
        self.plot.redraw()

    def game_changed (self, model, ply):
        if len(self.plot)+model.lowply > ply:
            return

        for i in range(len(self.plot)+model.lowply, ply):
            if i in model.scores:
                points = model.scores[i][1]
            else:
                points = leval.evaluateComplete(model.getBoardAtPly(i).board, WHITE)
            self.plot.addScore(points)

        if model.status == DRAW:
            points = 0
        elif model.status == WHITEWON:
            points = sys.maxsize
        elif model.status == BLACKWON:
            points = -sys.maxsize
        else:
            if ply in model.scores:
                points = model.scores[ply][1]
            else:
                points = leval.evaluateComplete(model.getBoardAtPly(ply).board, WHITE)
        self.plot.addScore(points)

        # As shownChanged will normally be emitted just after game_changed -
        # if we are viewing the latest position - we can do the selection change
        # now, and thereby avoid redraw being called twice
        if self.plot.selected == ply-model.lowply -1:
            self.plot.select(ply-model.lowply)
        self.plot.redraw()

        # Uncomment this to debug eval function
        return

        board = model.boards[-1].board
        opboard = model.boards[-1].clone().board
        opboard.setColor(1-opboard.color)
        material, phase = leval.evalMaterial (board)
        if board.color == WHITE:
            print("material", -material)
            e1 = leval.evalKingTropism (board)
            e2 = leval.evalKingTropism (opboard)
            print("evaluation: %d + %d = %d " % (e1, e2, e1+e2))
            p1 = leval.evalPawnStructure (board, phase)
            p2 = leval.evalPawnStructure (opboard, phase)
            print("pawns: %d + %d = %d " % (p1, p2, p1+p2))
            print("knights:",-leval.evalKnights (board))
            print("king:",-leval.evalKing(board,phase))
        else:
            print("material", material)
            print("evaluation:",leval.evalKingTropism (board))
            print("pawns:", leval.evalPawnStructure (board, phase))
            print("pawns2:", leval.evalPawnStructure (opboard, phase))
            print("pawns3:", leval.evalPawnStructure (board, phase) + \
                             leval.evalPawnStructure (opboard, phase))
            print("knights:",leval.evalKnights (board))
            print("king:",leval.evalKing(board,phase))
        print("----------------------")

    def game_started(self, model):
        self.game_changed(model, model.ply)

    def shownChanged (self, boardview, shown):
        if not boardview.shownIsMainLine():
            return
        if self.plot.selected != shown:
            self.plot.select(shown-self.boardview.model.lowply)
            self.plot.redraw()
            adj = self.sw.get_vadjustment()

            y = self.plot.moveHeight*(shown-self.boardview.model.lowply)
            if y < adj.get_value() or y > adj.get_value() + adj.get_page_size():
                adj.set_value(min(y, adj.get_upper()-adj.get_page_size()))

    def analysis_changed (self, gamemodel, ply):
        if self.boardview.animating:
            return

        if not self.boardview.shownIsMainLine():
            return
        if ply-gamemodel.lowply > len(self.plot.scores)-1:
            # analysis line of yet undone position
            return

        color = (ply-1) % 2
        score = gamemodel.scores[ply][1]
        score = score * -1 if color==WHITE else score
        self.plot.changeScore(ply-gamemodel.lowply, score)
        self.plot.select(ply)
        self.plot.redraw()

    def plot_selected (self, plot, selected):
        try:
            board = self.boardview.model.boards[selected]
        except IndexError:
            return
        self.boardview.setShownBoard(board)


class ScorePlot (Gtk.DrawingArea):

    __gtype_name__ = "ScorePlot"+str(randint(0,sys.maxsize))

    __gsignals__ = {
        "selected" : (GObject.SignalFlags.RUN_FIRST, None, (int,))
    }

    def __init__ (self, boardview):
        GObject.GObject.__init__(self)
        self.boardview = boardview
        self.connect("draw", self.expose)
        self.connect("button-press-event", self.press)
        self.props.can_focus = True
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.KEY_PRESS_MASK)
        self.moveHeight = 12
        self.scores = []
        self.selected = 0

    def addScore (self, score):
        self.scores.append(score)

    def changeScore(self, ply, score):
        if self.scores:
            self.scores[ply] = score

    def __len__ (self):
        return len(self.scores)

    def undo (self):
        del self.scores[-1]

    def select (self, index):
        self.selected = index

    def clear (self):
        del self.scores[:]

    @idle_add
    def redraw (self):
        if self.get_window():
            a = self.get_allocation()
            rect = Gdk.Rectangle()
            rect.x, rect.y, rect.width, rect.height = (0, 0, a.width, a.height)
            self.get_window().invalidate_rect(rect, True)
            self.get_window().process_updates(True)

    def press (self, widget, event):
        self.grab_focus()
        self.emit('selected', event.y/self.moveHeight)

    def expose (self, widget, context):
        a = widget.get_allocation()
        context.rectangle(a.x, a.y,
                          a.width, a.height)
        context.clip()
        self.draw(context)
        self.set_size_request(-1, (len(self.scores))*self.moveHeight)
        return False

    def draw (self, cr):
        m = self.boardview.model
        if m.isPlayingICSGame():
            return

        width = self.get_allocation().width
        height = len(self.scores)*self.moveHeight

        ########################################
        # Draw background                      #
        ########################################

        cr.set_source_rgb (1, 1, 1)
        cr.rectangle(0, 0, width, height)
        cr.fill()

        ########################################
        # Draw dark middle line                #
        ########################################

        cr.set_source_rgb (1, 0, 0)
        cr.move_to(width/2., 0)
        cr.line_to(width/2., height)
        cr.set_line_width(0.25)
        cr.stroke()

        ########################################
        # Draw the actual plot (dark area)     #
        ########################################

        sign = lambda n: n == 0 and 1 or n/abs(n)
        if self.scores:
            mapper = lambda score: (e**(-5e-4*abs(score))-1) * sign(score)
            cr.set_source_rgb (0, 0, 0)
            cr.move_to(width, 0)
            cr.line_to(width/2 - width/2*mapper(self.scores[0]), 0)
            for i, score in enumerate(self.scores):
                x = width/2 - width/2*mapper(score)
                y = (i+1) * self.moveHeight
                cr.line_to(x, y)
            cr.line_to(width, height)
            cr.fill_preserve()

        ########################################
        # Draw light middle line               #
        ########################################

        cr.save()
        cr.clip()
        cr.set_source_rgb (1, 1, 1)
        cr.move_to(width/2., 0)
        cr.line_to(width/2., height)
        cr.set_line_width(0.15)
        cr.stroke()
        cr.restore()

        ########################################
        # Draw selection                       #
        ########################################

        lw = 2.
        cr.set_line_width(lw)
        y = (self.selected)*self.moveHeight
        cr.rectangle(lw/2, y-lw/2, width-lw, self.moveHeight+lw)
        sc = self.get_style_context()
        found, color = sc.lookup_color("p_bg_selected")
        cr.set_source_rgba (color.red, color.green, color.blue, .15)
        cr.fill_preserve()
        cr.set_source_rgb (color.red, color.green, color.blue)
        cr.stroke()
