#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Dec 22 13:33:18 2017

@author: m75380
"""

import itertools 

from .helpers import _gen_body

class SingleQueryTemplate():
    '''Class representing an Elasticsearch query template'''
    def __init__(self, bool_lvl, source_col, ref_col, analyzer_suffix, boost):
        self.bool_lvl = bool_lvl
        
        if isinstance(source_col, str):
            source_col = [source_col]
            
        if isinstance(ref_col, str):
            ref_col = [ref_col]
            
        self.source_col = tuple(sorted(source_col))
        self.ref_col = tuple(sorted(ref_col))
        self.analyzer_suffix = analyzer_suffix
        self.boost = boost
        
        self.core = self._core()
        self.extended_core = self._extended_core()
        

    def to_dict(self):
        '''Returns a dict representation of the class'''
        return {'bool_lvl': self.bool_lvl,
                'source_col': self.source_col,
                'ref_col': self.ref_col,
                'analyzer_suffix': self.analyzer_suffix,
                'boost': self.boost}
        
    @classmethod
    def from_dict(cls, dict_):
        '''
        Returns an instance of the class using a representation generated by to_dict
        '''
        query_template = cls(dict_['bool_lvl'], 
                                   dict_['source_col'], 
                                   dict_['ref_col'], 
                                   dict_['analyzer_suffix'], 
                                   dict_['boost'])
        query_template.core = query_template._core()
        return query_template

    def _core(self):
        '''
        Return the minimal compononent that guarantees equivalence of the claim:
        "this query has results"    
        '''
        return (self.source_col, self.ref_col, self.analyzer_suffix)
        
    def _extended_core(self):
        '''
        Like _core with additional information on bool level
        '''
        return (self.bool_lvl, self.source_col, self.ref_col, self.analyzer_suffix)
    
    def __hash__(self):
        try:
            return self.__hash
        except:
            self.__hash = hash((self.bool_lvl, tuple(sorted(self.source_col)), tuple(sorted(self.source_col)), 
                     self.analyzer_suffix, self.boost))
            return self.__hash
    
    def __eq__(self, other):
        return self._as_tuple() == other._as_tuple()
    

    def __gt__(self, other):
        return self._as_tuple() > other._as_tuple()

#    def __str__(self):
#        return self._as_tuple().__str__()
    
    def _as_tuple(self):
        return (self.bool_lvl, self.source_col, self.ref_col, self.analyzer_suffix, self.boost)

class CompoundQueryTemplate():
    '''Information regarding a query to be used in the labeller'''
    def __init__(self, single_query_templates_tuple):        
        self.musts = tuple(sorted([SingleQueryTemplate(*x) for x in single_query_templates_tuple if x[0] == 'must']))
        self.shoulds = tuple(sorted([SingleQueryTemplate(*x) for x in single_query_templates_tuple if x[0] == 'should']))
        
        assert self.musts

        self.core = self._core()
        self.extended_core = self._extended_core()
        self.parent_cores = self._parent_cores()

    def add_must(self, query):
        self.musts = tuple(sorted(list(self.musts) + [query]))
        self.core = self._core()
        
    def add_should(self, query):
        self.shoulds = tuple(sorted(list(self.shoulds) + [query]))

    def to_dict(self):
        '''Returns a dict representation of the instance'''
        return [x.to_dict() for x in self.musts + self.shoulds]
        
    @classmethod
    def from_dict(cls, dict_):
        '''
        Returns an instance of the class using a representation generated by to_dict
        
        NB: dict_ is really a ml
        '''
        single_query_templates_tuple = [SingleQueryTemplate.from_dict(d)._as_tuple() \
                                        for d in dict_]
        return cls(single_query_templates_tuple)

    def _core(self):
        '''Same as in SingleQueryTemplate'''
        cores = []
        for must in self.musts:
            cores.append(must._core())
        return tuple(sorted(cores))

    def _extended_core(self):
        '''Compound query template without boost information'''
        extended_cores = []
        for must in self.musts:
            extended_cores.append(must._extended_core())
        return tuple(sorted(extended_cores))

    def __hash__(self):
        try:
            return self.__hash
        except:
            self.__hash = hash((q.__hash__() for q in self.musts + self.shoulds))    
            return self.__hash

    def __eq__(self, other):        
        bool_1 = self.musts == other.musts
        bool_2 = self.shoulds == other.shoulds
        
        return bool_1 and bool_2


    def __len__(self):
        return len(self.musts) + len(self.shoulds)

#    def __str__(self):
#        return [x.__str__() for x in self.musts + self.shoulds].__str__()

    def _parent_cores(self):
        '''Returns the core of all possible parents'''
        if len(self.core) == 1:
            return []
        else:
            to_return = []
            for i in range(1, len(self.core)-1):
                to_return.extend(list(itertools.combinations(self.core, i)))
            return to_return        
    
    
    def _gen_body(self, row, must_filter={}, must_not_filter={}, num_results=3):
        '''
        Generate the string to pass to Elastic search for it to execute query
        
        INPUT:
            - row: pandas.Series from the source object
            - must: terms to filter by field (AND: will include ONLY IF ALL are in text)
            - must_not: terms to exclude by field from search (OR: will exclude if ANY is found)
            - num_results: Max number of results for the query
        
        OUTPUT:
            - body: the query as string
        '''
        
        # TODO: do we use this ?
        body = _gen_body(self.musts + self.shoulds, row, must_filter, must_not_filter, num_results)
        return body
        
    def _as_tuple(self):
        return tuple(x._as_tuple() for x in self.musts + self.shoulds)