# -*- coding: utf-8 -*-

import numpy as np
import pandas as pd

from  sklearn.cluster import KMeans
from sklearn.cross_validation import StratifiedKFold
from sklearn.preprocessing import StandardScaler 
from sklearn.preprocessing import OneHotEncoder 
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import LabelBinarizer
from sklearn.preprocessing import MultiLabelBinarizer

import random

#compute euclid dist


class Allot():
    '''
     Intelligent case allocation algorithm 
    '''
    def __init__(self):
        print(self.__str__())
        print("".join(['-']*85))
        print('\n')

    def __str__(self):
        return "欢迎体验联信智能分案 مرحبا بكم في  برنامج  ذكي приветствие к Интеллектуальному опыту"
    __repr__ = __str__

    

    def EuclidDis1(self,A,B):
        '''
        ==============================================================
        Description: A method to compute two matrix's euclid distance.

        Usage：EuclidDis1(A,B)
        ==============================================================
        Arguments:

            A: A matrix
            B: A matrix
        the method return a np.matrix object.
        '''
        BT = B.transpose()
        vecProd = A * BT
        SqA =  A.getA()**2
        sumSqA = np.matrix(np.sum(SqA, axis=1))
        sumSqAEx = np.tile(sumSqA.transpose(), (1, vecProd.shape[1]))    
        SqB = B.getA()**2
        sumSqB = np.sum(SqB, axis=1)
        sumSqBEx = np.tile(sumSqB, (vecProd.shape[0], 1))    
        SqED = sumSqBEx + sumSqAEx - 2*vecProd   
        ED = (SqED.getA())**0.5
        return np.matrix(ED)

    #model1

    def recomm(self,dfold,df,bh,ywy,topn,ncluster,epsilon=0.001,init='k-means++',random_state=123,max_iter=1000,algorithm="auto"):
        '''
        =============================================================================================
        Description: The method which can assign cases to a salesman.

        Usage：recomm(dfold,df,bh,ywy,topn,ncluster,epsilon=0.001)
        =============================================================================================
        Arguments:

            dfold:     A DataFrame, the history case feature which this ywy has option.
            df:        A DataFrame, the new case feature which ready to assign.
            bh:        A list, the new case's id number which is one-to-one correspondence with df.
            ywy:       A string, the salesman's id number.
            topn:      An int number, how many cases should assign to the salesman.
            ncluster:  An int number, how many class should cluster.
            epsilon:   A little real number which avoid algorithmic errors
            ......
            -----------------------------------------------------------------------------------------
            more arguments find in the function of KMeans.
        Require:
            import numpy as np
            import pandas as pd
            from  sklearn.cluster import KMeans

        '''
        clf = KMeans(n_clusters=ncluster,init = init,random_state=random_state,max_iter=max_iter,algorithm=algorithm).fit(dfold)

        A = np.matrix(clf.cluster_centers_)
        B = np.matrix(df)
        dist = self.EuclidDis1(A, B)
        dist = np.array(dist)
        

        dist1 = dist.copy()
        dist_sort = pd.DataFrame({'bh':bh,'cluster':list(np.argmax(dist1,axis=0)),'distance':list(np.max(dist1,axis=0))})
        N = len(list(dist_sort.bh))
        Ni = pd.DataFrame(dist_sort.cluster.value_counts())
        Ni.reset_index(inplace=True)
        Ni.rename(columns={"index":0,"cluster":1},inplace=True)
        #Ni =pd.DataFrame(np.array(Ni0))
        Ni.rename(columns={0:'class',1:'counts'},inplace=True)

        dist_sort["group_sort"] = dist_sort['distance'].groupby(dist_sort['cluster']).rank(ascending=1,method='first')
        
        dist_sort = dist_sort.merge(Ni,left_on='cluster',right_on='class',how="left")
              
        dist_sort1 = dist_sort.groupby('cluster').apply(lambda x: x[x.group_sort <= topn*x.counts/(float(N)+epsilon)])    #可能会出bug
     
        recommend = pd.DataFrame({'bh':np.array(dist_sort1['bh']),'ywy':list(np.unique(ywy))*len(np.array(dist_sort1['bh']))})
        
        return recommend



    #model2

    def icic(self,dfold0,df0,topn0,bhywy0,ncluster,shuffle=0,epsilon=0.001,init='k-means++',random_state=123,max_iter=1000,algorithm="auto",path=0):
        '''
        ==========================================================================
        Description: The method which can assign cases to more than one salesmans.

        Usage：icic(dfold0,df0,shuffle=0,topn0,bhywy0,ncluster,...)
        ==========================================================================

        Arguments:

                dfold0:      A DataFrame, the history case feature which ywys has option.
                             dfold0 should incloud 'ywy0' and 'ajbh'and other features.

                df0:         A DataFrame, the new case feature which ready to assign.df0 should
                             incloud 'ajbh' and other features that the same as dfold0.

                shuffle:     0 or 1, if 1,then random disorder of the order of df0 and bhywy0.

                topn0:       A DataFrame,inclouding two cols like: 'ywy0' and 'topn_ywy',topn_ywy means 
                             how many cases should assign to this ywy0.+ topn_money

                bhywy0:      A DataFrame,inclouding two or one cols like: 'ywy0' and 'ajbh', and you can 
                             not provide the 'ajbh' some times.

                path:        0 or a string like '~/desktop/files.xlsx',it is a Excel file you want to save,
                             if path=0,means you do not need to save the assign result to you CP or disk.
      
                ......
                ------------------------------------------------------------------------------------------------
                more arguments find in the function of recomm. You can use help(recomm) or ?(recomm) to get
                support.
        Require:
            import numpy as np
            import pandas as pd
            from  sklearn.cluster import KMeans
            import random
        '''

        recommend_last  =pd.DataFrame({"bh":[],"ywy":[]})
        df0 = pd.DataFrame(df0)
        dfold0 = pd.DataFrame(dfold0)
        topn0 = pd.DataFrame(topn0)
        bhywy0 = pd.DataFrame(bhywy0)

        if shuffle==1:
            nrowdf0 = range(df0.shape[0])
            random.shuffle(list(nrowdf0))
            df0 = df0.iloc[nrowdf0]

            nrowbhywy0 = range(bhywy0.shape[0])
            random.shuffle(list(nrowbhywy0))
            bhywy0 = bhywy0.iloc[nrowbhywy0]
        else:
            pass


        dflst = df0.copy()
        ywylist = list(np.unique(list(bhywy0.ywy0)))


        for ywy in ywylist:
            if len(list(df0.ajbh)) >=1:
                dfold = dfold0[dfold0.ywy0==ywy]
                dfold1 = dfold.drop("ywy0",axis=1)
                dfold2 = dfold1.drop("ajbh",axis=1)
                bh = list(df0.ajbh)
                df2 =df0.drop('ajbh',axis=1)
                topn = int(np.unique(list(topn0[topn0.ywy0== ywy].topn_ywy)))

                recommend = self.recomm(dfold2,df2,bh,ywy,topn,ncluster,epsilon=0.001,init='k-means++',random_state=123,max_iter=1000,algorithm="auto")
                recommend_last = recommend_last.append(recommend)
        
                df0 = df0[np.invert(df0['ajbh'].isin(list(recommend.bh)))]
                

        recommend_last.reset_index(drop=True,inplace=True)
        set1 = set(recommend_last.bh)
        set2 = set(dflst.ajbh)
        if len(set2-set1) == 0:
            recommend_last1 = recommend_last
        else: 
            #这一块的设计好像不大合理
            dfno=pd.DataFrame({'bh':list(set2-set1),'ywy':list(np.random.choice ( ywylist,size=len(set2-set1)))})
            frames = [recommend_last,dfno]
            recommend_last1 = pd.concat(frames)

        if path == 0:
            pass
        else:
            writer = pd.ExcelWriter(path, engine='xlsxwriter')
            recommend_last1.to_excel(writer, sheet_name='Sheet1')
            writer.save()
     

        #return pd.DataFrame(recommend_last1)
       
       


    #Training

    def acc_mean(self,dfold0,bhywy0,topn0,ncluster,n_folds=5,shuffle=True,random_state=33,shuffle_icic=0,epsilon=0.001,init='k-means++',max_iter=1000,algorithm="auto",path=0):
     
        '''
        =============================================================================================
        Description: The method which can training the icic model 

        Usage：acc_mean(labely,dfold0,bhywy0,ncluster,n_folds=5,shuffle=True,......)
        =============================================================================================
        Arguments:

            labely:   the training dataset label.
            n_folds:  the CV params.
            shuffle:  the CV params.
            ......
            -----------------------------------------------------------------------------------------
            more arguments find in the function of icic. You can use help(icic) or ?(icic) to get
            support.
        Require:
            from sklearn.cross_validation import StratifiedKFold

        '''

        mean_acc=[]
        labely = list(dfold0.ywy0) 
        skf = StratifiedKFold(labely,n_folds=n_folds,shuffle =shuffle,random_state=random_state)
        for train_index,test_index in skf:
            
            dfold1 = dfold0.iloc[train_index]
            bhywy_test_real =  dfold0.iloc[test_index][['ywy0','ajbh']]
            df1 = dfold0.iloc[test_index].drop('ywy0',axis=1)
            bhywy1 = bhywy0.iloc[train_index]

            topn1 = topn0[topn0['ywy0'].isin (list(bhywy1.ywy0))]
            
            validdata = icic(dfold1,df1,topn1,bhywy1,ncluster,shuffle=shuffle_icic,epsilon=epsilon,init=init,random_state=random_state,max_iter=max_iter,algorithm=algorithm,path=path)
        
            predata = pd.merge(validdata,bhywy_test_real,left_on='bh',right_on='ajbh',how='left')
            postive1 = 0
            for i in np.arange(0,np.shape(predata)[0]):
               
                if predata.ywy[i]==predata.ywy0[i]:
                    postive1+=1       
            acc = float(postive1)/len(test_index)
        mean_acc.append(acc)
        mean_acc1 = np.mean(mean_acc)
        
        return mean_acc1


    #  输入的参数中包含，法务列表，在手案量和户数，产能系数，参数分案的手次
    # 考虑户数均衡

    def Confunc(self,ywy_table,new_case_capcity,new_case_money):
        '''
        ywy_table:
            ywy0: 业务员编号
            coef : 产能系数（0-2）
            coef_money:金额系数（0-2）
            handle_time : 分配的手次
            newornot: 是否是新法务

        new_case_capaity: 新案户数
        new_case_money: 新案金额

        ywy_table_history:
            ywy0:业务员编号
            case_c: 在手户数
            case_money: 在手金额

        返回： 每个法务本次应该分配的具体金额和户数(作为输入参数)
        '''

        #inhand_hs = np.sum(list(ywy_table_history['case_c'])) + new_case_capcity
        inhand_hs = new_case_capcity
        n_ywy = len(list(ywy_table['ywy0']))
        # base_hs = (np.ceil(np.float(inhand_hs) / n_ywy * np.array(list(ywy_table['coef']))) - 
        #     np.array(list(ywy_table_history['case_c'])))
        base_hs = np.ceil(np.float(inhand_hs) / n_ywy * np.array(list(ywy_table['coef'])))

        # inhand_money = np.sum(list(ywy_table_history['caseMoney'])) + new_case_money
        
        inhand_money = new_case_money
        base_money = np.ceil(np.float(inhand_money) / n_ywy * np.array(list(ywy_table['coef_money']))) 

        return pd.DataFrame({'ywy0':list(ywy_table.ywy0),'topn_ywy':list(base_hs),'topn_money':list(base_money),"newornot":list(ywy_table.newornot)}) 


