# -*- coding: utf-8 -*-
from collections import namedtuple
import itertools
import numpy

from ml import ML


"""Action namedtuple used to differentiate map transformation from filter op"""
Action = namedtuple('action', ['type', 'f'])


def read_csv(path, sep, line_terminator):
    """Yield a value from a csv file on disk

    Keyword arguments:
    path            (string) -- path to file
    sep             (string) -- separator substring
    line_terminator (string) -- line terminator substring
    """

    with open(path) as fin:

        for line in fin:
            yield line.replace('"', '').replace(line_terminator, '').split(sep)


def read_file(fin, sep, line_terminator):
    """Yield a value from a csv file in memory

    Keyword arguments:
    path            (string) -- path to file
    sep             (string) -- separator substring
    line_terminator (string) -- line terminator substring
    """

    fin.seek(0)

    for line in fin:
        yield line.replace('"', '').replace(line_terminator, '').split(sep)


def apply_action(data, action):
    """Compose successive generators from ordered map and filter transformation
    """

    if action.type == 'FLOW::MAP':
        return (action.f(_) for _ in data)

    elif action.type == 'FLOW::FILTER':
        return (_ for _ in data if action.f(_))

    else:
        return data


class Flow(object):

    def __init__(self, seed=1, flow=None):
        """Init Flow object"""

        self.data = None if flow is None else flow.regenerate_generator()
        self.source = None if flow is None else flow.source
        self.chain = [] if flow is None else list(flow.chain)

        self.header = [] if flow is None else list(flow.header)
        self.clfs = {} if flow is None else dict(flow.clfs)

        self.seed = seed if flow is None else flow.seed

        self.random_state = numpy.random.RandomState(self.seed)

    def from_csv(self, path, sep=',', line_terminator='\n'):
        """Hook Flow to disk csv source

        Keyword arguments:
        path            (string) -- path to file
        sep             (string) -- separator substring
        line_terminator (string) -- line terminator substring
        """

        self.data = read_csv(path, sep, line_terminator)
        self.source = (read_csv, path, sep, line_terminator)

        return self

    def from_file(self, fin, sep=',', line_terminator='\n'):
        """Hook Flow to memory csv source

        Keyword arguments:
        path            (string) -- path to file
        sep             (string) -- separator substring
        line_terminator (string) -- line terminator substring
        """

        self.data = read_file(fin, sep, line_terminator)
        self.source = (read_file, fin, sep, line_terminator)

        return self

    def map(self, f):
        """Register a map transformation of function f

        Keyword arguments:
        f               (function) -- function with signature x => value
        """

        self.chain += [Action('FLOW::MAP', f)]

        return self

    def filter(self, f):
        """Register a filter transformation of function f

        Keyword arguments:
        f               (function) -- function with signature x => boolean
        """

        self.chain += [Action('FLOW::FILTER', f)]

        return self

    def reduce(self, f):
        """Reduce data with function f

        Keyword arguments:
        f               (function) -- function with signature a, b => value
        """

        return reduce(f, self.flow().data)

    def eval(self):
        """Evaluate dataset

        Apply all successive transformations and return resulting dataset
        """

        return [_ for _ in self.flow().data]

    def batch(self, size):
        """Evaluate dataset

        Apply all successive transformations and return resulting dataset slice

        Keyword arguments:
        size            (int) -- size of slice
        """

        return list(itertools.islice(self.flow().data, size))

    def flow(self):
        """Compose generator from successive registered actions"""

        for a in self.chain:
            self.data = apply_action(self.data, a)

        return self

    def regenerate_generator(self):
        """Renew the self.data generator"""

        return self.source[0](*self.source[1:])

    def reload(self):
        """Renew self.data and self.random_state"""

        self.random_state = numpy.random.RandomState(self.seed)
        self.data = self.regenerate_generator()

        return self

    def __repr__(self):
        """Evaluate whole dataset and return string representation"""

        return '\n'.join([
            str(_) for _ in self.eval()
        ]) if self.data is not None else 'None'

    def tensorize(self, target=''):
        """Return data and target column if the latter is given"""

        M = self.eval()
        if target != '':
            return [
                [x for i, x in enumerate(_) if i != self.header.index(target)]
                for _ in M
            ], [
                [
                    x for i, x in enumerate(_)
                    if i == self.header.index(target)
                ][0] for _ in M
            ]
        else:
            return M

    def header_is(self, names=[]):
        """Set header"""

        self.header = names
        return self

    def register_ml(self, ml):

        self.clfs[
            ml.meta['name']
        ] = ml

        return self

    def fit_with(self, classifier, name='', target=''):
        """Fit classifier to data"""

        X, y = self.tensorize(target)
        clf = ML(classifier, name)

        clf.fit(X, y)
        return self.register_ml(clf)

    def score_with(self, name='', target=''):

        X, y = self.tensorize(target)

        self.clfs[name].log_loss(X, y)

        return self

@@ -4541,32 +4541,506 @@
    return self%0A%0A
+    def split(self, rate=0.5, on='left'):%0A        %22%22%22Split data long rate and select one part%0A%0A        Keyword arguments:%0A        rate    (float) -- number between 0 and 1 splitting data%0A        on      ('left' or 'right') -- choose which part of the split to take%0A        %22%22%22%0A%0A        self.map(lambda x: x + %5B%0A            self.random_state.choice(%5B'left', 'right'%5D, p=%5Brate, 1.0 - rate%5D)%0A        %5D).filter(lambda x: x%5B-1%5D == on).map(lambda x: x%5B:-1%5D)%0A%0A        return self%0A%0A
     def __repr__
@@ -5863,21 +5863,208 @@
 ter_
-ml(self, ml):
+clfs(self, clfs):%0A        %22%22%22Register clfs dict from another flow%22%22%22%0A%0A        self.clfs.update(clfs)%0A%0A        return self%0A%0A    def register_ml(self, ml):%0A        %22%22%22Register a new ML object in flow%22%22%22
 %0A%0A  
@@ -6418,16 +6418,61 @@
 get=''):
+%0A        %22%22%22Score data with clf named name%22%22%22
 %0A%0A      

Add register classifier method to flow.py