# -*- coding: utf-8 -*-
from ambrogio.plugin import Plugin
import datetime
import re
import numpy as np

class Treasurer(Plugin):
    
    def receive_message(self, ambrogio, message):
        
        regex1 = r'^(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2})))\|([A-Z]{2,13})((((\+(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?)((\*(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?))|(((\*(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?)((\+(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?)))((,[A-Z]{2,13})((((\+(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?)((\*(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?))|(((\*(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?)((\+(([1-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2}))))?))))*( \".*\")?$'
        regex2 = r'^HISTORY$'
        regex3 = r'^BALANCE$'
        regex4 = r'^CREATE ([A-Z]{3,12})$'
        regex5 = r'^ADD [A-Z]{2} [A-Z]{3,12}$'
        regex6 = r'^DELETE [A-Z]{2} [A-Z]{3,12}$'
        
        
        pattern1 = re.compile(regex1)
        pattern2 = re.compile(regex2)
        pattern3 = re.compile(regex3)
        pattern4 = re.compile(regex4)
        pattern5 = re.compile(regex5)
        pattern6 = re.compile(regex6)


        m1 = pattern1.match(message.text)
        m2 = pattern2.match(message.text)
        m3 = pattern3.match(message.text)
        m4 = pattern4.match(message.text)
        m5 = pattern5.match(message.text)
        m6 = pattern6.match(message.text)

        if m1:
            #[Customer]
            
            patternm1 = re.compile('(?<=\||\,)\w{1,}')
            Objects = patternm1.findall(message.text)
            patternm2 = re.compile('(?<=\||\,)\w{2}(?![A-Z])')
            Customers = patternm2.findall(message.text)
            groupcandidates=re.findall('(?<=\||\,)\w{3,}',message.text)
            
            # to check if there exsit repeated objects in the command
            for i in range(0,len(Objects)):
                if (Objects[i]) in (Objects[:i]) or ((Objects[i]) in (Objects[i+1:])):
                    return 0
        
            # to check if there exsit any group input in the command before adding any groups and if there are any customers input
            if (len(groupcandidates) and not(len(self.GROUP))) or (len(self.MemberNames)==1):
                return 0

            # if the Groups have been created, to check if the group input in the command is belonging to the exsiting groups
            # and add the new customers from the groups input into the Customer set
            count=0
            for i in range(0,len(groupcandidates)):
                for j in range(0,len(self.GROUP)):
                    if (groupcandidates[i] in self.GROUP[j]):
                        count=+1
                        id = self.GROUP[j]
                        break
                if not count:
                    return 0
                else:
                    for l in range(1,len(id)):
                       Customers.append(id[l])
                count=0
            # to see if there exsit repeated customers in the command
            for i in range(0,len(Customers)):
                if (Customers[i]) in (Customers[:i]) or ((Customers[i]) in (Customers[i+1:])):
                    return 0

            #[payer]
            payer = [message.sender]

            #[amout]
            amount=re.findall(r'(\d+\.?\d*)\|',message.text)
        
            #[note]
            Note=re.findall(r'"(.+?)"',message.text)
            if not(len(Note)):
               Note=' '
               self.NOTE.append((Note[0]))
            else:
               self.NOTE.append(' '+str(Note[0])+' ')
            
            # uneven payment information: [Plus] and [Star] vector
            Plus = [0]*len(Customers)
            Star = [1]*len(Customers)
            # consider if there are exsiting groups
            if len(self.GROUP):
                for i in range(len(Customers)):
                    for j in range(0,len(self.GROUP)):
                        if not(Customers[i] in self.GROUP[j]):
                           base_string = Customers[i]
                           re_plus = ('%s\+([0-9]\d*\.?\d*)' % base_string )
                           re_star = ('%s\*([0-9]\d*\.?\d*)' % base_string )
                           string_plus = re.findall(re_plus, message.text)
                           string_star = re.findall(re_star, message.text)
                           if string_plus:
                              Plus[i] = float(string_plus[0])
                        
                              # check if followed by *number
                              re_star = ('(?<=%s\+%s\*)([0-9]\d*\.?\d*)' %(Customers[i],string_plus[0]))
                              string_star = re.findall(re_star, message.text)
                              if string_star:
                                 Star[i] = float(string_star[0])
                           elif string_star:
                              Star[i] = float(string_star[0])
                              re_plus = ('(?<=%s\*%s\+)([0-9]\d*\.?\d*)' %(Customers[i],string_star[0]))
                              string_plus = re.findall(re_plus, message.text)
                              if string_plus:
                                 Plus[i] = float(string_plus[0])
                        # calculating the uneven vectors for the members inside groups
                        else:
                           base_string = self.GROUP[j]
                           re_plus = ('%s\+([0-9]\d*\.?\d*)' % base_string )
                           re_star = ('%s\*([0-9]\d*\.?\d*)' % base_string )
                           string_plus = re.findall(re_plus, message.text)
                           string_star = re.findall(re_star, message.text)
                           if string_plus:
                              Plus[i] = float(string_plus[0])
                                       
                              # check if followed by *number
                              re_star = ('(?<=%s\+%s\*)([0-9]\d*\.?\d*)' %(self.GROUP[j],string_plus[0]))
                              string_star = re.findall(re_star, message.text)
                              if string_star:
                                 Star[i] = float(string_star[0])
                           elif string_star:
                              Star[i] = float(string_star[0])
                              re_plus = ('(?<=%s\*%s\+)([0-9]\d*\.?\d*)' %(self.GROUP[j],string_star[0]))
                              string_plus = re.findall(re_plus, message.text)
                              if string_plus:
                                 Plus[i] = float(string_plus[0])
            # consider if there aren't any exsiting groups
            else:
                 for i in range(len(Customers)):
                    base_string = Customers[i]
                    re_plus = ('%s\+([0-9]\d*\.?\d*)' % base_string )
                    re_star = ('%s\*([0-9]\d*\.?\d*)' % base_string )
                    string_plus = re.findall(re_plus, message.text)
                    string_star = re.findall(re_star, message.text)
                    if string_plus:
                       Plus[i] = float(string_plus[0])
                
                       # check if followed by *number
                       re_star = ('(?<=%s\+%s\*)([0-9]\d*\.?\d*)' %(Customers[i],string_plus[0]))
                       string_star = re.findall(re_star, message.text)
                       if string_star:
                          Star[i] = float(string_star[0])
                    elif string_star:
                       Star[i] = float(string_star[0])
                       re_plus = ('(?<=%s\*%s\+)([0-9]\d*\.?\d*)' %(Customers[i],string_star[0]))
                       string_plus = re.findall(re_plus, message.text)
                       if string_plus:
                          Plus[i] = float(string_plus[0])
            answer = self.add_expense(ambrogio, message, payer, Customers, amount, Plus, Star ,Note)
            ambrogio.send_text(answer)
            
        elif m2: # HISTORY
            user = [message.sender]
            answer =  self.show_history(ambrogio,message, user)
            ambrogio.send_text(answer)
            		
        elif m3: # BALANCE
            answer =  self.show_balance(ambrogio, message)
            ambrogio.send_text(answer)
        elif m4: # CREAT
            group = re.findall(r' ([A-Z]{3,12})', message.text)
            # add group name if not registered before
            if (not (group[0] in self.GROUPNAME)):
               answer =  self.creatgroup(ambrogio, message, group)
               self.GROUPNAME.append(group[0])
               ambrogio.send_text(answer)

        elif m5: # ADD_MEMBER
            groupmember = re.findall(r' ([A-Z]{2}) ', message.text)
            groupname = re.findall(r' ([A-Z]{3,12})', message.text)
            # add group name if not registered before
            for name in self.GROUP:
                idx=self.GROUP.index(name)
                if (groupname[0] in name) and not(groupmember[0] in name):
                    answer =  self.addmemeber(ambrogio, message, groupmember, groupname, idx)
                    ambrogio.send_text(answer)
        
        elif m6: # DELETE_MEMBER
            groupmember = re.findall(r' ([A-Z]{2}) ', message.text)
            groupname = re.findall(r' ([A-Z]{3,12})', message.text)
            # delete group name if not registered before
            for name in self.GROUP:
                idx=self.GROUP.index(name)
                if (groupname[0] in name) and (groupmember[0] in name):
                    answer =  self.deletememeber(ambrogio, message,groupmember,groupname,idx)
                    ambrogio.send_text(answer)
        else:
            return 0

    def init_plugin(self, ambrogio):
        self.MemberNames = []
        self.expense = []
        self.expense_time = []
        self.contribution = []
        self.balance = []
        self.owinglist =[]
        #set HISTORY initial values
        self.payHistory = []
        self.PAYER=[]
        self.NOTE=[]
        self.CUSTOMERS=[]
        self.EXP=[]
        self.HISTORY=[]
        #set Group Initial values
        self.GROUP=[]
        self.GROUPNAME=[]

    def add_expense(self, ambrogio, message, payer, Customers,amount, Plus, Star, Note):
        
        # add history
        self.payHistory.append(message.text)
        dt=datetime.datetime.now()
        self.expense_time.append(dt.strftime("%d/%m/%y"))
        self.PAYER.append(payer)
        self.NOTE.append(Note)
        self.CUSTOMERS.append(Customers)
        amount=float(amount[0])

        # update the financial status
        for names in payer+Customers:
            # add to members if not registered before
            if not(names in self.MemberNames):
                self.MemberNames.append(names)
                self.expense.append(0)
                self.contribution.append(0)
        exp_base = (amount - sum(Plus))/sum(Star)     
        Exp= np.array(Star)*exp_base + np.array(Plus)     # expense of each Customer
        self.EXP.append(Exp)

        i = 0;
        for names in Customers:
            # update the expense for each customer
            self.expense[ self.MemberNames.index(names)] += round(Exp[i],2)
            i+=1

        # update the contribution for each customer
        self.contribution[ self.MemberNames.index(payer[0])] += amount
        return "done"

    def show_balance(self, ambrogio, message):

        # built the balance
        self.balance = []
        self.owinglist = []
        
        for names in self.MemberNames:
            idx = self.MemberNames.index( names)
            self.balance.append(self.contribution[idx] - self.expense[idx])
        # construct the owing list
        # To minimize the number of transaction, a greedy algorithm is implemented. We define the degree of unbalance as the sum of all owing amount to be paid. At each step, the algorithm picks the transaction leading to the fast decrease of the degree of unbalance, this is realised by picking the member owing the most to pay the member being owed the most

        balance = np.array(self.balance)
        deg_ub = np.abs(balance.max() - balance.min()) # degree of unbalance

        while deg_ub>0:
                # pick the member to pay and the member to be paid
                com=deg_ub
                i_to_pay = balance.argmin()
                i_be_paid = balance.argmax()
                debt = min( - balance[i_to_pay], balance[i_be_paid])
                if not(round(debt, 2)==0.00):
                   balance[i_to_pay]+= debt
                   balance[i_be_paid]-= debt
                   self.owinglist.append( self.MemberNames[i_to_pay] + ' owes ' + self.MemberNames[i_be_paid]+ ' '+ "%.2f" % round(debt, 2) )
                #update the degree of unbalance
                   deg_ub = np.abs(balance.max() - balance.min())
                   if (com==deg_ub):
                      break
                else:
                   break
                
        owingListString =''
        for i in range(len(self.owinglist)):
            owingListString += self.owinglist[i] + '\n' 
        return owingListString

    def show_history(self, ambrogio,message, user):
        del self.HISTORY[:]
        
        for i in range(len(self.PAYER)):
              if (user == self.PAYER[i]):

                  if not((str(user[0]) in self.CUSTOMERS[i])):
                         self.HISTORY.append (self.expense_time[i] + self.NOTE[2*i] + '-' + ' '+ 'you get back' + ' ' + "%.2f" % np.sum(self.EXP[i]) )
                  else:
                         user_loc= self.CUSTOMERS[i].index(str(user[0]))
                         zo=self.EXP[i]
                         self.HISTORY.append (self.expense_time[i] + self.NOTE[2*i] + '-' + ' '+ 'you get back' + ' ' + "%.2f" % ((np.sum(self.EXP[i]))-zo[user_loc]) )
              else:
                  if (str(user[0]) in self.CUSTOMERS[i]):
                         user_loc= self.CUSTOMERS[i].index(str(user[0]))
                         zo=self.EXP[i]
                         self.HISTORY.append (self.expense_time[i] + self.NOTE[2*i] + '-' + ' '+ 'you pay back' + ' ' + "%.2f" % zo[user_loc] )

        history=''
        for i in range(len(self.HISTORY)):
            history += self.HISTORY[i] + '\n'
        return history

    def creatgroup(self, ambrogio, message,group):
        #Creat a new group
        self.GROUP.append(group)
        return "done"

    def addmemeber(self, ambrogio, message,groupmember,groupname,idx):
        #Add a memeber into group
        self.GROUP[idx].append(groupmember[0])
        return "done"
 
    def deletememeber(self, ambrogio, message,groupmember,groupname,idx):
        #Delete a member from group
        self.GROUP[idx].remove(groupmember[0])
        return "done"






