import cPickle
from errors import *
from itertools import izip


class OffenseRules(object):
    def __init__(self, log, pkl):
        self.missing_codes = [9999, 999, 9, 9, 9, 99, 9]
        self.unknown_codes = [9999, 999, 9, 9, 9, 99, 9]
        self.not_coded = [-9999, -999, -9, -9, -9, -99, -9]
        self.default_pred_codes = [9999, 999, 9, 9, 9, 99, 9]
        self.vars = ['ncic', 'chg', 'inc', 'dom', 'weap', 'cdv', 'pg']
        self.log = log
        self.pickle_dump = lambda d: cPickle.dump(d, pkl)
        
        self.missing_map = zip(self.vars, self.missing_codes)
        self.missing_dict = dict([[var, missing] for var, missing in self.missing_map])
        self.unknown_map = zip(self.vars, self.unknown_codes)
        self.unknown_dict = dict([[var, unknown] for var, unknown in self.unknown_map])
        self.not_coded_map = zip(self.vars, self.unknown_codes)
        self.not_coded_dict = dict([[var, unknown] for var, unknown in self.unknown_map])
        
    def convert_inc(self, row, vars, ruleset, log_name):
        key = '|'.join(ruleset.metadata.get_key(row))
        if vars['inc'] is None:
            if key:
                try:
                    if ruleset:
                        return ruleset.convert(key)
                    else:
                        # if no crosswalk defined than any value other than missing is logged
                        self.log.write(log_name, [row['id'], row['rapstatex'], key, 
                                                  "New value for key (%s)" % '|'.join([k.name for k in ruleset.metadata.key_meta])])
                except ConvertError:
                    self.log.write(log_name, [row['id'], row['rapstatex'], key, 
                                              "New value for key (%s)" % '|'.join([k.name for k in ruleset.metadata.key_meta])])
                    return self.unknown_dict['inc']
            else:
                return self.missing_dict['inc']
        else:
            return vars['inc']
    
    def _convert_offenses(self, key, crosswalk, skip_coding=[], raise_missing_error=False):
        convert_key = '|'.join(key)
        missing_key = True
        temp_vars = {}
        
        for x in key:
            # if one of the values in the key is Not None than missing_key set to false and we break the loop
            if x: 
                missing_key = False
                break
        
        if missing_key:
            # assign missing values when all variables in the key are None
            if raise_missing_error:
                raise ConvertError('missing key')

            for k, v in self.missing_map:
                if k not in skip_coding:
                    temp_vars[k] = v
                else:
                    temp_vars[k] = None
        else:
            for k, v in izip(self.vars, crosswalk.convert(convert_key, split='|')):
                temp_vars[k] = v
        
        return temp_vars
        
    def convert_offenses(self, row, rulesets, rule_order, _index=0, full_key=None, ground_truth={}):
        """
        @param rules: The crosswalk object containing all the RuleSets.
        @param rule_order: A list of tuples (x,y) where x = key to locate in RuleSet and y = name of RuleSet.
                      Length of list determines depth of recursion.
        @param skip_missing_coding: A list containing the names of offense variables to not code 
                                    as missing if no match is found in crosswalk. 
        """
        #TODO remove "recursion" because not really recursion
        vars = {}
        ruleset = rulesets[rule_order[_index]]

        if 'remove' in rule_order[_index]:
            # if a remove crosswalk is detected apply the delete_transform
            _key = ruleset.delete_transform(ruleset.metadata.get_key(row, as_str=True))
            _row = dict([(ruleset.metadata.key_meta[i].name, k) for i,k in enumerate(_key.split('|'))])
            #then use the transformed key in the next ruleset
            _index += 1
            ruleset = rulesets[rule_order[_index]]

            for key_meta in ruleset.metadata.key_meta:
                # add any new key variables to the _row object
                if key_meta.name not in _row:
                    _row[key_meta.name] = row[key_meta.name]

            key = ruleset.metadata.get_key(_row)
        else:
            key = ruleset.metadata.get_key(row)

        if not full_key:
            full_key = (ruleset.metadata.keys.keys(), ruleset.metadata.keys.values())
        else:
            for k, v in ruleset.metadata.keys.iteritems():
                if k not in full_key[0]:
                    full_key[0].append(k)
                    full_key[1].append(v)

        index = _index + 1

        try:
            # code ancic, achg, ainc, adom, aweap, acdv, apg using key in ruleset
            raise_missing = index != len(rule_order)
            vars = self._convert_offenses(key, ruleset, skip_coding=ruleset.skip_missing, raise_missing_error=raise_missing)

        except ConvertError:
            # check to see if we need to recurse
            if index == len(rule_order):
                # output unknown values
                for k, v in self.unknown_map:
                    vars[k] = None

                #log no match using key and predictions
                #TODO make full_key that contains all the key values for all the rulesets
                self.log.write(self.log.var,
                               [row['id'], row['rapstatex'],
                                '|'.join([_k.name for _k in sorted(full_key[1], key= lambda k: k.order)]),
                                '|'.join([_k.get_key(row) for _k in sorted(full_key[1], key= lambda k: k.order)]),
                                "key not found in crosswalk_%s" % rule_order[_index][1]],
                               type=2)
            else:
                # dive down one layer
                return self.convert_offenses(row, rulesets, rule_order, index, full_key)

        return vars