import math
import collections
import json

from poobrains import Markup, app, abort, g, locked_cached_property

import poobrains.storage
import poobrains.auth
import poobrains.commenting

from . import base

class Dataset(poobrains.commenting.Commentable):


    title = poobrains.storage.fields.CharField()
    description = poobrains.md.MarkdownField(null=True)
    label_x = poobrains.storage.fields.CharField(verbose_name="Label for the x-axis")
    label_y = poobrains.storage.fields.CharField(verbose_name="Label for the y-axis")
    #grid_step_x = poobrains.storage.fields.DoubleField(default=1.0)
    #grid_step_y = poobrains.storage.fields.DoubleField(default=1.0)

    @property
    def ref_id(self):
        return "dataset-%s" % self.name


    @locked_cached_property
    def authorized_datapoints(self):
        return Datapoint.list('read', g.user).where(Datapoint.dataset == self)

    
    def datapoint_id(self, datapoint):
        return "dataset-%s-%s" % (self.name, datapoint.x)

    
    def plot(self):

        return Plot(datasets=[self]).render('inline')


class DatapointFieldset(poobrains.form.Fieldset):

    def __init__(self, datapoint, **kwargs):

        super(DatapointFieldset, self).__init__(**kwargs)

        self.datapoint = datapoint
        self.x = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.x, placeholder=Datapoint.x.verbose_name, help_text=Datapoint.x.help_text)
        self.y = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.y, placeholder=Datapoint.y.verbose_name, help_text=Datapoint.y.help_text)
        self.error_lower = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.error_lower, placeholder=Datapoint.error_lower.verbose_name, help_text=Datapoint.error_lower.help_text)
        self.error_upper = poobrains.form.fields.Text(type=poobrains.form.types.FLOAT, value=self.datapoint.error_upper, placeholder=Datapoint.error_upper.verbose_name, help_text=Datapoint.error_upper.help_text)


    def process(self, submit, dataset):

        if self.datapoint in dataset.datapoints:
            pass # florp
        else:
            self.datapoint.dataset = dataset
            self.datapoint.x = self.fields['x'].value
            self.datapoint.y = self.fields['y'].value
            self.datapoint.error_lower = self.fields['error_lower'].value
            self.datapoint.error_upper = self.fields['error_upper'].value

            self.datapoint.save(force_insert=True)


class Datapoint(poobrains.auth.Owned):

    class Meta:
        order_by = ['dataset', 'x']
        primary_key = poobrains.storage.CompositeKey('dataset', 'x')
        related_use_form = True

    dataset = poobrains.storage.fields.ForeignKeyField(Dataset, related_name='datapoints')
    x = poobrains.storage.fields.DoubleField()
    y = poobrains.storage.fields.DoubleField()
    error_lower = poobrains.storage.fields.FloatField(help_text="Lower margin of error", default=0.0)
    error_upper = poobrains.storage.fields.FloatField(help_text="Upper margin of error", default=0.0)


@app.expose('/svg/plot')
class Plot(base.SVG):

    padding = None
    width = None
    height = None
    inner_width = None
    inner_height = None
    plot_width = None
    plot_height = None
    description_height = None

    datasets = None
    min_x = None
    max_x = None
    min_y = None
    max_y = None
    span_x = None
    span_y = None
        

    class Meta:

        modes = collections.OrderedDict([
            ('teaser', 'read'),
            ('full', 'read'),
            ('raw', 'read'),
            ('json', 'read'),
            ('inline', 'read')
        ])

    def __init__(self, handle=None, mode=None, datasets=None, **kwargs):

        super(Plot, self).__init__(handle=handle, mode=mode, **kwargs)

        if handle is None and datasets is None:
            abort(404, "No datasets selected")
        
        self.padding = app.config['SVG_PLOT_PADDING']
        self.plot_width = app.config['SVG_PLOT_WIDTH']
        self.plot_height = app.config['SVG_PLOT_HEIGHT']
        self.description_height = app.config['SVG_PLOT_DESCRIPTION_HEIGHT']
        self.width = self.plot_width + (2 * self.padding)
        self.height = self.plot_height + self.description_height + (3 * self.padding)
        self.inner_width = self.width - (2 * self.padding)
        self.inner_height = self.height - (2 * self.padding)

        if datasets:
            self.datasets = datasets

        else:

            self.datasets = []
            dataset_names = handle.split(',')

            for name in dataset_names:

                ds = Dataset.load(name)
                if ds.permissions['read'].check(g.user):
                    self.datasets.append(ds)

        self.handle = ','.join([ds.name for ds in self.datasets]) # needed for proper URL generation

        datapoint_count = 0
        for datapoint in Datapoint.list('read', g.user).where(Datapoint.dataset << self.datasets):

            datapoint_count += 1

            y_lower = datapoint.y
            if datapoint.error_lower:
                y_lower -= datapoint.error_lower

            y_upper = datapoint.y
            if datapoint.error_upper:
                y_upper += datapoint.error_upper


            if self.min_x is None or datapoint.x < self.min_x:
                self.min_x = datapoint.x

            if self.max_x is None or datapoint.x > self.max_x:
                self.max_x = datapoint.x
               
            if self.min_y is None or y_lower < self.min_y:
                self.min_y = y_lower

            if self.max_y is None or y_upper > self.max_y:
                self.max_y = y_upper

        if datapoint_count > 0:
            self.span_x = self.max_x - self.min_x
            self.span_y = self.max_y - self.min_y

        else:
            self.min_x = 0
            self.max_x = 0
            self.min_y = 0
            self.max_y = 0
            self.span_x = 0
            self.span_y = 0


    def render(self, mode=None):

        if mode == 'json':

            data = {}

            for dataset in self.datasets:
                data[dataset.name] = []

                for datapoint in dataset.authorized_datapoints:
                    data[dataset.name].append({
                        'x': datapoint.x,
                        'y': datapoint.y,
                        'error_lower': datapoint.error_lower,
                        'error_upper': datapoint.error_upper
                    })

            return Markup(json.dumps(data))

        return super(Plot, self).render(mode=mode)


    def normalize_x(self, value):

        if self.span_x == 0.0:
            return self.plot_width / 2.0

        return (value - self.min_x) * (self.plot_width / self.span_x)


    def normalize_y(self, value):

        if self.span_y == 0.0:
            return self.plot_height / 2.0

        return self.plot_height - (value - self.min_y) * (self.plot_height / self.span_y)


    @property
    def label_x(self):

        return u' / '.join([dataset.label_x for dataset in self.datasets])


    @property
    def label_y(self):

        return u' / '.join([dataset.label_y for dataset in self.datasets])


    @property
    def grid_x(self):

        if self.span_x == 0:
            return [self.min_x]

        grid_step = 10 ** (int(math.log10(self.span_x)) - 1)

        offset = (self.min_x % grid_step) * grid_step # distance from start of plot to first line on the grid
        start = self.min_x + offset

        x = start
        coords = [x]
        while x <= self.max_x:
            coords.append(x)
            x += grid_step

        return coords


    @property
    def grid_y(self):

        if self.span_y == 0:
            return [self.min_y]

        grid_step = 10 ** (int(math.log10(self.span_y)) - 1)

        offset = (self.min_y % grid_step) * grid_step # distance from start of plot to first line on the grid
        start = self.min_y + offset

        y = start
        coords = [y]
        while y <= self.max_y:
            coords.append(y)
            y += grid_step

        return coords
