#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
from os import path, makedirs
import copy

import PIL
import PIL.Image
import PIL.ImageDraw
import PIL.ImageColor
import PIL.ImageFont
from stocks3.core.sharedregion import SHARED_REGION


small_graph_settings = {}
small_graph_settings['width'] = 230
small_graph_settings['height'] = 130
small_graph_settings['margin'] = 10
small_graph_settings['LabelCount'] = 4
small_graph_settings['stepsv'] = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10,
                                  20, 50, 100, 200, 500, 1000, 2000, 5000,
                                  10000, 20000, 50000, 100000, 250000, 500000,
                                  1000000, 2500000, 5000000, 10000000]

small_graph_settings['whole'] = {'fill': 'white'}
small_graph_settings['area'] = {'stroke': 'black', 'fill': 'white'}

small_graph_settings['text'] = {'font': 'fonts/Arial.ttf',
                                'stroke': None,
                                'fill': 'black',
                                'pointsize': 9,
                                'style': 'normal',
                                'weight': 0,
                                'align': 'center',
                                'antialias': True}

small_graph_settings['axis'] = {'stroke': 'gray', 'fill': None, 'antialias': True}

small_graph_settings['month_color'] = 'darkblue'
small_graph_settings['next_month_color'] = 'green'

small_graph_settings['is_thick_curve'] = False

big_graph_settings = {'width': 500, 'height': 300, 'margin': 10,
                      'stepsv': [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10,
                                 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000,
                                 20000, 50000, 100000, 250000, 500000, 1000000,
                                 2500000, 5000000, 10000000], 'whole': {'fill': 'white'},
                      'area': {'stroke': 'black', 'fill': 'white'}, 'text': {'font': 'fonts/Arial.ttf',
                                                                             'stroke': None,
                                                                             'fill': 'black',
                                                                             'pointsize': 10,
                                                                             'style': 'normal',
                                                                             'weight': 0,
                                                                             'align': 'center',
                                                                             'antialias': True},
                      'axis': {'stroke': 'gray', 'fill': None, 'antialias': True}, 'month_color': 'darkblue',
                      'next_month_color': 'green', 'is_thick_curve': True}

MONTH_NAMES = ['январь', 'февраль', 'март', 'апрель', 'май', 'июнь', 'июль',
               'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь']


# class for drawing 2D graphic for stocks values on some date interval
class Graph2File(object):
    def __init__(self, templater):
        # Используется для сохранения данных
        # NOTE: templater - это L{PngTemplater}
        self._templater = templater
        # graph settings for big 2D graphic
        self._big_graph_settings = big_graph_settings
        # graph settings for small 2D graphic
        self._small_graph_settings = small_graph_settings

        # month names
        self._month_name = MONTH_NAMES

        # number of points to draw
        self._big_point_count = 30
        self._small_point_count = 7

    # strip array to num_points values
    def _norm_coord(self, x, y, num_points):
        # print "_normCoord, x: ", x
        # print "_normCoord, y: ", y
        # print "_normCoord, num_points: ", num_points
        # we need not more than _big_point_count points on x axis
        len_x = len(x)
        # print("len_x: ", len_x)
        x_norm = []
        if len_x > num_points:
            first_index = len_x - num_points
            for i in range(first_index, len_x):
                x_norm.append(x[i])
                # print "slice x_norm: ", x_norm
        else:
            x_norm = x

        # print("x_norm: ", x_norm)

        # we need not more than _big_point_count points on y axis
        y_norm = []  # array of array for y coordinate
        for i in y:
            len_y = len(i)
            # print("len_y: ", len_y)

            if len_y <= num_points:
                y_norm.append(i)
            else:
                first_index = len_y - num_points
                # print("first_index: ", first_index)

                i_norm = []
                for j in range(first_index, len_y):
                    i_norm.append(i[j])

                y_norm.append(i_norm)

        # print("y_norm: ", y_norm)

        return x_norm, y_norm

    # generate month day labels
    def _genxLabels(self, x):
        # generate x axis labels ( month days )
        x_labels = []
        for i in range(0, len(x)):
            x_labels.append(x[i].strftime("%d"))
        return x_labels

    # generate month name labels
    def _genMonthLabels(self, x):
        month_index = -1
        month = x[0].month

        for i in range(0, len(x) - 1):
            if x[i].month != x[i + 1].month:
                month_index = i
                month = x[i].month

        if month_index == -1:
            return [{'name': self._month_name[month - 1], 'index': (0, len(x) - 1)}]
        else:
            return [
                {'name': self._month_name[month - 1], 'index': (0, month_index)},
                {'name': self._month_name[month % 12], 'index': (month_index + 1, len(x) - 1)},
            ]

    # prepare data for drawing graphics
    def _prepareData(self, x, y, num_points):
        gr_data = {}  # data for drawing graphic

        # we need not more than _big_point_count points on x axis
        (x_norm, y_norm) = self._norm_coord(x, y, num_points)
        # print "after prepare data: x_norm: ", x_norm
        # print "after prepare data: y_norm: ", y_norm

        # x, y values
        gr_data['x'] = x_norm
        gr_data['y'] = y_norm

        # labels for month days
        gr_data['x_labels'] = self._genxLabels(x_norm)
        # print "after _genxLabels: ", gr_data['x_labels']

        # labels for month names
        gr_data['month_labels'] = self._genMonthLabels(x_norm)
        # print "after _genMonthLabels: ", gr_data['month_labels']

        return gr_data

    # draw big graphic
    def _bigDraw(self, big_name, graph_id, x, y, region_id):
        try:
            gr_data = self._prepareData(x, y, self._big_point_count)  # data for drawing graphic

            self._draw(big_name, gr_data, self._big_graph_settings)

            return big_name
        # FIXME
        except Exception as e:
            # Logger.exception( "error while drawing big graphic" )
            raise

    # draw small graphic
    def _smallDraw(self, small_name, graph_id, x, y, region_id):
        try:
            gr_data = self._prepareData(x, y, self._small_point_count)  # data for drawing graphic

            self._draw(small_name, gr_data, self._small_graph_settings)

            return small_name
        # FIXME
        except Exception as e:
            # Logger.exception( "error while drawing small graphic" )
            raise

    def _prepare_filename(self, template, graph_id, region_id):
        # FIXME: этот код дублируется, похожее есть в item
        template = template.format(id=str(graph_id))
        if region_id == SHARED_REGION:
            return template
        else:
            return template.format(region=region_id)

    # draw small and big graphic, return names of output png graphic files [ name_of_small_picture, name_of_big_picture ]
    def draw(self, graph_id, x, y, region_id=0):
        res = []

        if len(x) < 2:
            return []

        small_name = self._templater.prepare_filename(self._templater.small_template,
                                                      graph_id, region_id)

        filename = self._smallDraw(small_name, graph_id, x, y, region_id)
        if filename:
            res.append(filename)

        big_name = self._templater.prepare_filename(self._templater.big_template,
                                                    graph_id, region_id)

        filename = self._bigDraw(big_name, graph_id, x, y, region_id)
        if filename:
            res.append(filename)

        return res

    # get mininimum and maximum values for y
    def _getMinMax(self, y):
        minv = y[0][0]
        maxv = minv
        for i in y:
            y_tmp = copy.copy(i)
            y_tmp.sort()

            if minv > y_tmp[0]:
                minv = y_tmp[0]

            if maxv < y_tmp[-1]:
                maxv = y_tmp[-1]

        return minv, maxv

    # get y step on Y axis
    def _getYstep(self, deltav, label_count, stepsv):
        lcount = label_count
        ystep = stepsv[0]  # deltav / lcount

        available_steps = []  # possible steps
        for i in stepsv:
            count = int(deltav / i)  # count of y steps
            if 0 < count <= lcount:
                available_steps.append((count, i))
            if count == 0:
                break

        if len(available_steps) == 0:
            return deltav / label_count

        min_diff = lcount
        for i in available_steps:
            s_c = i[0]
            diff = lcount - s_c
            if min_diff > diff:
                min_diff = diff
                ystep = i[1]
        return ystep

    # generate y labels array - labels for Y axis
    def _genYlabels(self, minv, maxv, ystep):
        ylabels = []  # labels on Y axis

        basev = int(minv / ystep) * ystep  # rounding value

        i = 0
        while True:
            tmp = basev + ystep * i

            l_str = "%g" % tmp
            ylabels.append(l_str)

            # Logger.debug( "push ylabel: %s", l_str )

            if tmp >= maxv:
                break

            i += 1

        return ylabels

    def _drawMonthLabels(self, gr_data, gr_setts, draw, fnt):
        # print("_drawMonthLabels started")

        month_labels = gr_data['month_labels']
        yc = gr_data['by'] + gr_data['ry'] + gr_data['vmrg'] + 2

        # print("_drawMonthLabels yc: ", yc)

        whole_str = ''
        for i in month_labels:
            whole_str = whole_str + i['name']

        # print("_drawMonthLabels whole_str: ", whole_str)

        (sp_w, sp_h) = draw.textsize('  ', font=fnt)
        all_space_width = sp_w * (len(month_labels) - 1)

        # print("_drawMonthLabels all_space_width: ", all_space_width)

        (twidth, theight) = draw.textsize(whole_str, font=fnt)

        xc = gr_data['bx'] + (gr_data['rx'] - twidth - all_space_width) / 2
        # print("_drawMonthLabels xc: ", xc)

        text_width = 0  # length of previous string

        # draw all month labels
        for i in range(0, len(month_labels)):
            m_name = month_labels[i]['name']
            m_name_unicode = m_name

            # print('i = %d, m_name = %s' % (i, m_name ))
            xc = xc + text_width

            # print("_drawMonthLabels xc = %d, yc = %d, rx = %d, ry = %d, by = %d, vmrg = %d" % ( xc, yc, gr_data['rx'], gr_data['ry'], gr_data['by'], gr_data['vmrg'] ))

            if i == 0:  # this is first month
                # print "_drawMonthLabels drawing text for this month: ", m_name
                draw.text((xc, yc), m_name_unicode, font=fnt, fill=PIL.ImageColor.getrgb(gr_setts['month_color']))
                # print "_drawMonthLabels done"
            else:
                # print "_drawMonthLabels drawing text for next month: ", m_name
                draw.text((xc, yc), m_name_unicode, font=fnt, fill=PIL.ImageColor.getrgb(gr_setts['next_month_color']))
                # print "_drawMonthLabels done"

            (w, h) = draw.textsize(m_name_unicode, font=fnt)
            text_width = w + sp_w

            # print "_drawMonthLabels: ended"

    # draw graph data - gr_data with graphic settings - gr_setts and save output to file - filename
    def _draw(self, filename, gr_data, gr_setts, region_id=0):
        # print "_draw: file=%s, gr_setts=%s" % ( filename, gr_setts )
        gp = gr_setts  # graphic properties

        # create image object
        img = PIL.Image.new('RGB', (gp['width'], gp['height']), PIL.ImageColor.getrgb("white"))
        # create draw object for drawing on img
        draw = PIL.ImageDraw.Draw(img)

        fnt = PIL.ImageFont.truetype(gp['text']['font'], gp['text']['pointsize'], encoding="cp1251")
        (twidth, theight) = draw.textsize("йу", font=fnt)

        vmrg = theight + 2  # vertical margin for axis
        gr_data['vmrg'] = vmrg
        # print "vmrg: ", vmrg

        by = gp['margin']  # base Y dot
        gr_data['by'] = by

        ry = int(gp['height'] - 2 * by - 2 * vmrg)  # height of drawing area
        gr_data['ry'] = ry

        # print("ry: ", ry)

        x = gr_data['x']
        y = gr_data['y']

        (minv, maxv) = self._getMinMax(y)

        deltav = (maxv - minv)
        if deltav <= 0:
            deltav = 1

        # choose axis step;
        lcount = 10
        if 'LabelCount' in gp:
            lcount = gp['LabelCount']

        # print("lcount: ", lcount)

        ystep = self._getYstep(deltav, lcount, gp['stepsv'])

        yscale = ry / (deltav + 2 * ystep)
        gr_data['yscale'] = yscale
        # print("yscale = %f" % ( yscale ))

        # generate labels on Y axis
        ylabels = self._genYlabels(minv, maxv, ystep)

        # get maximum y length string and calculate it length
        lbl_tmp = copy.deepcopy(ylabels)
        lbl_tmp.sort(key=lambda label: len(label))

        test_str = lbl_tmp[-1] + '*'
        # print("test_str: %s" % test_str)

        (twidth, theight) = draw.textsize(test_str, font=fnt)
        # print "twidth = %d, theight = %d" % ( twidth, theight )

        hmrg = twidth + 4  # horizontal margin for axis
        gr_data['hmrg'] = hmrg

        bx = int(gp['margin'] + hmrg)  # base X dot
        gr_data['bx'] = bx

        rx = int(gp['width'] - gp['margin'] - bx)  # width of drawing area
        gr_data['rx'] = rx
        # print("rx: ", rx)

        x_labels = gr_data['x_labels']
        xscale = float(rx) / float(len(x_labels) - 1)
        gr_data['xscale'] = xscale
        # print("rx = %d, xscale = %f" % ( rx, xscale ))

        # draw area rectangle
        draw.rectangle([(0, 0), (gp['width'], gp['height'])], fill=PIL.ImageColor.getrgb(gp['whole']['fill']))
        draw.rectangle([(bx, by), (bx + rx, by + ry)],
                       outline=PIL.ImageColor.getrgb(gp['area']['stroke']),
                       fill=PIL.ImageColor.getrgb(gp['area']['fill']))

        # Draw y-axis labels and horizontal grid lines
        margin = by
        line_color = PIL.ImageColor.getrgb(gp['axis']['stroke'])
        for i in ylabels:
            # print("i label: ", i)
            fi = float(i)
            yc = int(margin + ry - (fi - minv + ystep) * yscale)
            # print("yc: ", yc)

            if yc <= margin + 1 or yc >= margin + ry - 1:
                # print("yc under the margin")
                continue

            # draw horizontal grid lines
            x1 = bx + 1
            x2 = x1 + 2
            while x2 < bx + rx:
                draw.line([(x1, yc), (x2, yc)], fill=line_color)
                x1 = x2 + 2
                x2 = x1 + 2

            if x1 < bx + rx:
                draw.line([(x1, yc), (bx + rx, yc)], fill=line_color)

            # draw y-axis labels
            (twidth, theight) = draw.textsize(i, font=fnt)
            xc = (bx - twidth) / 2
            # print("xc: ", xc)

            draw.text((xc, yc - vmrg / 2), i, fill=PIL.ImageColor.getrgb(gp['text']['fill']), font=fnt)

        # draw vertical grid lines and x-axis labels
        month_labels = gr_data['month_labels']
        month_index = month_labels[0]['index']

        for i in range(0, len(x_labels)):
            xc = int(bx + float(i) * xscale)
            # print "i = %d, xscale = %f, rx = %d, xc = %d" % ( i, xscale, rx, xc )
            # draw vertical gridline
            y1 = by + 1
            y2 = y1 + 2
            while y2 < by + ry:
                draw.line([(xc, y1), (xc, y2)], fill=line_color)
                y1 = y2 + 2
                y2 = y1 + 2

            if y1 < by + ry:
                draw.line([(xc, y1), (xc, by + ry)], fill=line_color)

            if i == month_index[1] + 1:
                draw.line([(xc - 1, by), (xc - 1, by + ry)], fill=line_color)
                draw.line([(xc, by), (xc, by + ry)], fill=line_color)
                draw.line([(xc + 1, by), (xc + 1, by + ry)], fill=line_color)

            # draw x-axis label
            (twidth, theight) = draw.textsize(x_labels[i], font=fnt)
            ty = by + ry + 2
            tx = xc - twidth / 2

            if month_index[0] <= i <= month_index[1]:  # this day in first month
                draw.text((tx, ty), x_labels[i], fill=PIL.ImageColor.getrgb(gp['month_color']), font=fnt)
            else:  # this in next month
                draw.text((tx, ty), x_labels[i], fill=PIL.ImageColor.getrgb(gp['next_month_color']), font=fnt)

        self._drawMonthLabels(gr_data, gp, draw, fnt)

        # make array with curve lines
        curves = []  # array of curves
        w_curves = []  # array of curves for thick line
        ww_curves = []  # array of curves for very thick line

        # is curve must be thick
        f_thick_curve = gr_setts['is_thick_curve']

        for yi in y:
            points = []  # points for curve
            w_points = []  # points for width
            ww_points = []

            for i in range(0, len(x)):
                xc = int(bx + float(i) * xscale)
                yc = int(margin + ry - (float(yi[i]) - minv + ystep) * yscale)

                points.append(xc)
                points.append(yc)

                w_points.append(xc)
                w_points.append(yc + 1)

                if f_thick_curve:
                    ww_points.append(xc)
                    ww_points.append(yc - 1)

            curves.append(points)
            w_curves.append(w_points)

            if f_thick_curve:
                ww_curves.append(ww_points)

        # draw curve lines
        for i in range(0, len(curves)):
            if i == 0:
                draw.line(curves[i], fill=PIL.ImageColor.getrgb('red'))
                draw.line(w_curves[i], fill=PIL.ImageColor.getrgb('red'))
                if f_thick_curve:
                    draw.line(ww_curves[i], fill=PIL.ImageColor.getrgb('red'))
            else:
                draw.line(curves[i], fill=PIL.ImageColor.getrgb('black'))
                draw.line(w_curves[i], fill=PIL.ImageColor.getrgb('black'))
                if f_thick_curve:
                    draw.line(ww_curves[i], fill=PIL.ImageColor.getrgb('black'))

        # draw circles on curve line
        for i in range(0, len(curves)):
            for j in range(0, int(len(curves[i]) / 2)):
                xc = curves[i][2 * j]
                yc = curves[i][2 * j + 1]

                rect2 = (xc - 2, yc - 2, xc + 2, yc + 2)
                rect = (xc - 3, yc - 3, xc + 3, yc + 3)

                if i == 0:
                    draw.ellipse(rect, fill=PIL.ImageColor.getrgb('white'), outline=PIL.ImageColor.getrgb('red'))
                    if f_thick_curve:
                        draw.ellipse(rect2, fill=PIL.ImageColor.getrgb('white'), outline=PIL.ImageColor.getrgb('red'))
                else:
                    draw.ellipse(rect, fill=PIL.ImageColor.getrgb('white'), outline=PIL.ImageColor.getrgb('black'))
                    if f_thick_curve:
                        draw.ellipse(rect2, fill=PIL.ImageColor.getrgb('white'), outline=PIL.ImageColor.getrgb('black'))

        def save_img(fd, tmp):
            os.close(fd)
            img.save(tmp, "PNG")

        self._templater.with_temp(filename, save_img)
