"""classes to handle configuration files"""

import os, logging
from ConfigParser import SafeConfigParser
from collections import namedtuple

import numpy as np
import pandas as pd

from .. import archive

logging.getLogger(__name__)
log = logging.getLogger()

def _open_cfg(path):
    cfg = SafeConfigParser()
    if not len(cfg.read([path])):
        raise ValueError("cannot load %s" % path)
    return cfg

def _check_exist_f(s):
    if not os.path.isfile(s):
        import warnings
        warnings.warn("non-existent input file '%s'" % s)
    return s

def _check_get_pairs(s, tp=int):
    items = map(tp, s.split())
    p = []
    npairs = len(items) / 2
    for i in range(npairs):
        p.append( (items[i*2], items[i*2+1]) )
    return tuple(p)

def _check_get_singles(s, tp=int):
    return tuple( map(tp, s.split()) )

class CfgFactory( object ):
    """abstract class with factory method from a config file

    to subclass, need to define

    _types: types for properties
    _section: section on the confifg file"""

    @classmethod
    def _from_settings(cls, path, new=tuple.__new__, len=len):
        'make a new  object from a settins file'

        if not len(cls._fields) == len(cls._types):
            raise ValueError("a type (found %d) / key (found %d) needed for %s" % (len(cls._fields),
                len(cls._types), str(cls)))

        cfg = _open_cfg(path)
        iterable =  map(lambda (k,t): t( cfg.get(cls._section, k) ),
                        zip(cls._fields, cls._types))
        result = new(cls, iterable)
        if len(result) != len(cls._fields):
            raise TypeError('expected %d arguments, got %d' % (len(cls._fields),
                len(result)))
        result._check_consistency()
        return result

    def _check_consistency(self):
        "check as much as you can that values of params make sense"

        pass

## TODO: change this to CnnModelSpec
class ModelSpec (namedtuple('MetaParams', ("nkerns rfield pool lreg_size")), CfgFactory):
    __slots__ = ()

    _types = (_check_get_singles, _check_get_pairs, _check_get_pairs, int)
    _section = "model"

    def _check_consistency(self):
        if len(self.nkerns) !=  len(self.pool) or len(self.nkerns) != len(self.rfield):
            raise ValueError(" len(self.nkerns) !=  len(self.pool) or len(self.nkerns) != len(self.rfield)")

    @property
    def cp_arch(self):
        return (self.nkerns, self.rfield, self.pool)

class MtrainSpec( namedtuple("MtrainSpec", "batch_size l1_rate l2_rate lr tau momentum_mult nepochs minepochs patience"), CfgFactory ):
    __slots__ = ()

    _types = (int, float, float, float, int, float, int, int, int)
    _section = "modtrain"

    def _check_consistency(self):
        if self.minepochs > self.nepochs:
            raise ValueError("minepochs (%d) > nepochs (%d)", 
                             self.minepochs, self.nepochs)

#class DtrainSpec( namedtuple("DtrainSpec", "batch_size l1_rate l2_rate lr tau momentum_mult nepochs minepochs patience"), CfgFactory ):
#    __slots__ = ()
#
#    _types = (int, float, float, float, int, float, int, int, int)
#    _section = "dttrain"
#
#    def _check_consistency(self):
#        pass
#
#class OutputSpec( namedtuple("OutputSpec", "oid weight_log learn_log model_info best_inputX best_inputY best_example_inputX best_example_inputY"), CfgFactory ):
#    __slots__ = ()
#
#    _types = (str, str, str, str, str, str, str, str)
#    _section = "deliverables"
#
#    def _check_consistency(self):
#        pass

class AESpec(namedtuple("AutoEncoderSpec", "rec_error minepochs maxepochs batch_size noise"), CfgFactory):
    __slots__ = ()

    _types = (float, int, int, int, float)
    _section = "ae"

    def _check_consistency(self):
        pass

class DataSpec( namedtuple("DataSpec", "dataname tracks width train_s valid_s labels track_names label_names batch_size train_batches valid_batches") ):
    __slots__ = ()


    @classmethod
    def batches_from_data(self, tot_size, batch_s, valid_s, valid_idx, rng):
        if valid_s <= 0 or valid_s >= 1:
            raise ValueError("valid_s (%f) should be between (0, 1) ", valid_s)

        if batch_s > tot_size * min(valid_s, 1-valid_s):
            raise ValueError("batch size (%d) too big > min(valid_s=%d, train_s=%d)",
                             batch_s, tot_size * valid_s, tot_size *(1-valid_s))

        all_batches = range( tot_size / batch_s )
        try:
            valid_batches = all_batches[valid_idx:valid_idx+int(len(all_batches)*valid_s)]
        except IndexError:
            raise ValueError("valid_idx (%d) should be between 0 and %d",
                             valid_idx, len(all_batches)-1)
        train_batches = list( set(all_batches) - set(valid_batches) )
        assert set(train_batches + valid_batches) == set(all_batches)
        assert len( set(train_batches) & set(valid_batches) ) == 0
        rng.shuffle(train_batches)
        rng.shuffle(valid_batches)
        logging.info("train batches: %s", str(train_batches))
        logging.info("valid batches: %s", str(valid_batches))
        return (train_batches, valid_batches)

        
    @classmethod
    def _from_archive(cls, path, batch_s, rng, valid_s, valid_idx):
        """initialize from archive

        path: dataset specification path
        batch_s: batch size
        train_s: train size (default = 50%)
        valid_s: vlaidation size (default 25% )"""
        
        
        objX = archive.load_object( archive.archname(path),
                                   archive.basename(path) + "/X")
        objT = archive.load_object( archive.archname(path),
                                    archive.basename(path) + "/T")
        
        gene_names = objX.items
        track_names = objX.major_axis
        width = objX.minor_axis.shape[0]
        label_names = np.unique( objT["label_name"].values ).tolist()

        #store = pd.HDFStore( archive.archname(path) )
        #gene_names = store[ archive.basename(path)+"/X" ].items
        #track_names = store[ archive.basename(path)+"/X" ].major_axis
        #width = store[ archive.basename(path)+"/X" ].minor_axis.shape[0]
        #label_names = np.unique( store[ archive.basename(path)+"/T" ]["label_name"].values ).tolist()

        train_batches, valid_batches = cls.batches_from_data(len(gene_names),
                                                         batch_s, valid_s, 
                                                         valid_idx, rng)
        #store.close()
        return cls._make( (archive.basename(path), len(track_names), width,
            batch_s * len(train_batches), batch_s * len(valid_batches), len(label_names), 
            track_names, label_names, batch_s, train_batches, valid_batches) )


if __name__ == "__main__":
    pass



