<?php

require "EnhancedCommonChecker.php";
require "DuplicateAccount.php";

/**
 * 
 * @author Anton
 *
 */
class PotentialDuplicateAccountChecker{
    /**
     * 
     * @var integer
     */
    const MINIMUM_THRESHOLD = 4;
    
    /**
     * 
     * @var integer
     */
    const MAXIMUM_THRESHOLD = 20;
    
    /**
     * 
     * @var EnhancedCommonChecker
     */
    private $commonChecker;
    
    /**
     * 
     * @var DuplicateAccount
     */
    private $account1;
    
    /**
     * 
     * @var DuplicateAccount
     */
    private $account2;
    
    /**
     * 
     * will obtain the value of a JSON string and will be having the reason why an account marked as potential duplicate account.
     * 
     * @var Array
     */
    private $reasons = [];

    /**
     * The Constructor
     */
    public function __construct(){
        $this -> commonChecker = new EnhancedCommonChecker();
        $this -> commonChecker -> init();
    }
    
    /**
     * 
     * @param DuplicateAccount $account1
     * @param DuplicateAccount $account2
     */
    public function setAccounts(DuplicateAccount $account1, DuplicateAccount $account2) : void {
        $this -> reasons = [];
        
        $this -> account1 = $account1;
        $this -> account2 = $account2;
    }
    
    /**
     * 
     * max threshold : 1
     * 
     * @param String $phrase1
     * @param String $phrase2
     * @return int
     */
    private function randomPhraseThreshold(String $phrase1, String $phrase2, int $customThreshold = 1, String $desc = '', 
        String $index = "RandomChecking", &$goSignal = false) : int {
        $threshold = 0;
        $result = [];
        
        $cmc = $this -> calculate($phrase1, $phrase2, false);
        
        if($cmc){
            $config = [
                "percentage" => true,
                "function" => "commonlen"
            ];
            
            if($this -> hasExpectedLength($cmc, .7, $config, $result)){
                $threshold += $customThreshold;
                $goSignal = true;
                $this->reasons[$index] = [
                    "description" => "Matched Phrase Found".(!empty($desc) ? ": {$desc}" : ""),
                    "focus" => $cmc->focused,
                    "reference" => $cmc->source,
                    "result" => $result
                ];
            }
        }
        
        return $threshold;
    }
    
    private function exactPhraseThreshold(String $phrase1, String $phrase2, int $customThreshold = 1, String $desc = '', 
        $index = 'ExactPhraseChecking', &$goSignal = false) : int {
        
        $threshold = 0;
        
        if(strcasecmp($phrase1, $phrase2) === 0){
            $goSignal = true;
            $threshold += $customThreshold;
            
            $this->reasons[$index] = [
                "description" => "Matched Phrase Found".(!empty($desc) ? ": {$desc}" : ""),
                "focus" => $phrase1,
                "reference" => $phrase2
            ];
        }
            
        return $threshold;    
    }
    
    /**
     * 
     * max threshold : 2
     * 
     * @return int
     */
    private function emailAddressThreshold(&$goSignal = false) : int {
        $threshold = 0;
                
        $cmc = $this -> calculate($this->account1->emailAddress, $this->account2->emailAddress, false);
        
        if($cmc){
            $result = [];
            
            if($this -> hasExpectedLength($cmc, 3, [], $result)){
                $threshold += 2;
                $goSignal = true;
                $this->reasons["EmailAddress"] = [
                    "description" => "Email Address Matched",
                    "focus" => $cmc->focused,
                    "reference" => $cmc->source,
                    "result" => $result
                ];
            }
        }
              
        
        
        return $threshold;
    }
    
    /**
     * 
     * max threshold : 2
     * 
     * @return int
     */
    private function mailAddressThreshold(&$goSignal = false) : int {
        $threshold = 0; 
        $cmc = $this -> calculate($this->account1->homeAddress, $this->account2->homeAddress, false);
       
        if($cmc){
            $config = [
                "function" => "commonlen",
                "percentage" => true
            ];
            
            $result = [];
            
            if($this -> hasExpectedLength($cmc, .7, $config, $result)){
                $threshold += 2;
                $goSignal = true;
                $this->reasons["MailAddress"] = [
                    "description" => "Mail Address Matched",
                    "focus" => $cmc->focused,
                    "reference" => $cmc->source,
                    "result" => $result
                ];
            }
        }
      
        return $threshold;
    }
    
    /**
     * 
     * max threshold : 2
     * 
     * @param String $otherPhrase
     * @return int
     */
    private function firstNameThreshold(&$goSignal = false) : int {
        $threshold = 0;
       
        if(strcasecmp($this -> account2 -> firstName, $this -> account1 -> firstName) === 0){
            $threshold += 2;
            $goSignal = true;
            
            $this->reasons["FirstName"] = [
                "description" => "First Name Matched",
                "focus" => $this -> account1 -> firstName,
                "reference" => $this -> account2 -> firstName
            ];
        }else if(strpos(
            strtolower($this -> account2 -> firstName), 
            strtolower($this -> account1 -> firstName)
            ) !== false){
                $threshold += 2;
                $goSignal = true;
                
                $this->reasons["FirstName"] = [
                    "description" => "First Name Matched",
                    "focus" => $this -> account1 -> firstName,
                    "reference" => $this -> account2 -> firstName
                ];
        }
         
        return $threshold;
    }
    
    /**
     * 
     * max threshold : 2
     * 
     * @return int
     */
    private function lastNameThreshold(&$goSignal = false) : int {
        $threshold = 0;
        
        if(strcasecmp($this -> account2 -> lastName, $this -> account1 -> lastName) === 0){
            $threshold += 2;
            $goSignal = true;
            $this->reasons["LastName"] = [
                "description" => "Last Name Matched",
                "focus" => $this -> account1 -> lastName,
                "reference" => $this -> account2 -> lastName
            ];
        }else if(
            strpos(
                strtolower($this -> account2 -> lastName), 
                strtolower($this -> account1 -> lastName)
                ) !== false){
            $threshold += 2;
            $goSignal = true;
            $this->reasons["LastName"] = [
                "description" => "Last Name Matched",
                "focus" => $this -> account1 -> lastName,
                "reference" => $this -> account2 -> lastName
            ];
        }
            
        return $threshold;
    }
    
    /**
     *
     * max threshold : 2
     *
     * @return int
     */
    private function ipAddressThreshold(&$goSignal = false) : int {
        $threshold = 0;
        
        if(strcasecmp($this->account1->ipAddress, $this->account2->ipAddress) === 0){
            $threshold += 2;
            $goSignal = true;
            $this->reasons["IPAddress"] = [
                "description" => "IP Address Matched",
                "focus" => $this->account1->ipAddress,
                "reference" => $this->account2->ipAddress
            ];
        }
        
        return $threshold;
    }
    
    /**
     * 
     * max threshold : 1
     * 
     * @return int
     */
    private function dateOfBirthThreshold(&$goSignal = false, $format = "Y-m-d") : int {
        $threshold = 0;
        
        $dob1 = date($format, strtotime($this->account1->dateOfBirth));
        $dob2 = date($format, strtotime($this->account2->dateOfBirth));
        
        if($dob1 == $dob2){
            $threshold += 1;
            $goSignal = true;
            $this->reasons["DateOfBirth"] = [
                "description" => "Date of Birth Matched",
                "focus" => $dob1,
                "reference" => $dob2
            ];
        }
       
        return $threshold;
    }
    
    /**
     * 
     * @param String $str1
     * @param String $str2
     * @param boolean $toInvert
     * @return EnhancedCommonChecker
     */
    private function calculate(String $str1, String $str2, $toInvert = true){
        if(empty($str1) || empty($str2))
            return false;
        
        return $this -> commonChecker -> calculate($str1, $str2, $toInvert);
    }
    
    /**
     * 
     * @param EnhancedCommonChecker $cmc
     * @param float|int $expects
     * @param array $config
     * @return bool
     */
    private function hasExpectedLength(EnhancedCommonChecker $cmc, $expects, Array $config = [], &$result = null) : bool {
        
        if(empty($config)){
            $config = [
                "function" => "commons", 
                "percentage" => false
            ];
        }
            
        $found = false;
        
        if(!$config["percentage"]){
            // in order
            $array = $cmc->{$config["function"]}();
            
            foreach($array as $k => $v){
                $nospaces = str_replace(" ", "", $v["phrase"]);
                             
                if(strlen($nospaces) >= $expects){
                    $found = true;
                    
                    if(is_array($result)){
                        $result = [
                            "charfound" => $v,
                            "startsAt" => $k,
                            "commons" => $cmc->commons()
                        ];
                    }
                    
                    break;
                }
            }
        }else{
            // percentage base
            $plength = strlen($cmc->focused) * $expects;
            $commonl = $cmc->{$config["function"]}();
            
            if(is_array($result)){
                $result = [
                    "plength" => $plength,
                    "commonlen" => $commonl,
                    "commons" => $cmc->commons()
                ];
            }
            
            if($commonl >= $plength){
                $found = true;
            }
        }
        
        return $found;
    }
     
    /**
     *
     * aggregate thresholds came from common matcher
     *
     */
    private function calculateTotalThreshold(&$signals = null) : int {
        $threshold = 0;
        
        $threshold += $this -> emailAddressThreshold($emailGoSignal);
        $threshold += $this -> firstNameThreshold($fnameGoSignal);
        $threshold += $this -> lastNameThreshold($lnameGoSignal);
        $threshold += $this -> ipAddressThreshold($ipGoSignal);
        // $threshold += $this -> mailAddressThreshold($mailGoSignal);
        $threshold += $this -> dateOfBirthThreshold($dobGoSignal);
        
        /**
         * checks if there's an inverted naming found between potential duplicate account and reference.
         */
        if(!$fnameGoSignal){
            $threshold += $this -> exactPhraseThreshold($this->account1->lastName, $this->account2->firstName, 2, 
                "Last Name vs. First Name (Inverted)", "FirstNameInvert", $fnameGoSignal);
            $invertedFName = $fnameGoSignal;
        }
        
        /**
         * checks if there's an inverted naming found between potential duplicate account and reference.
         */
        if(!$lnameGoSignal){
            $threshold += $this -> exactPhraseThreshold($this->account1->firstName, $this->account2->lastName, 2, 
                "First Name vs. Last Name (Inverted)", "LastNameInvert", $lnameGoSignal);
            $invertedLName = $lnameGoSignal;
        }
        
        if(!$dobGoSignal)
            $threshold += $this -> dateOfBirthThreshold($dobMDGoSignal, "m-d");
        
        // full-name-to-other-attr-comparison
        $fullName = strtolower("{$this->account1->firstName} {$this->account1->lastName}");
        $threshold += $this -> randomPhraseThreshold($fullName, $this->account2->emailAddress, 1, "Full Name vs. Email Address", 
            "FullNameAndEmailAdd", $fullNameAndEmailGoSignal);
        
        if(is_array($signals)){
            $signals = [
                "email" => $emailGoSignal,
                "firstName" => $fnameGoSignal,
                "lastName" => $lnameGoSignal,
                "ipAddress" => $ipGoSignal,
                "dateOfBirth" => $dobGoSignal,
                "fullNameAndEmail" => $fullNameAndEmailGoSignal
            ];
            
            if(isset($invertedFName) && isset($invertedLName))
                $signals["inverted"] = $invertedFName && $invertedLName;
            
            if(!$dobGoSignal && $dobGoSignal != null)
                $signals["dateOfBirthMD"] = $dobMDGoSignal;
        }
                        
        return $threshold;
    }
    
    /**
     * 
     * @return String
     */
    public function reasons() : Array {
        return $this -> reasons;
    }
    
    /**
     * 
     * @param unknown $callback
     * @return bool
     */
    public function validateRegisteredAccount($callback = null) : bool {
       $signal = []; 
        
       $totalThreshold = $this -> calculateTotalThreshold($signal); 
                 
       $this->account2->reason = $this->reasons();
       
       $store = false;
          
       $fNameAndLNameFlag = ($signal["firstName"] || $signal["lastName"]);
       $dobFlag = ($signal["dateOfBirth"] || (isset($signal["dateOfBirthMD"]) && !empty($signal["dateOfBirthMD"])));
       
       if(isset($signal["inverted"]))
           $store = $signal["inverted"];
           
       if($fNameAndLNameFlag && $dobFlag)
           $store = true;
               
       if($fNameAndLNameFlag && $signal["ipAddress"])
           $store = true;
                   
       if($fNameAndLNameFlag && $signal["email"])
           $store = true;
                       
       if($signal["email"] && $dobFlag)
           $store = true;
                           
       if($signal["email"] && $signal["ipAddress"])
           $store = true;
                               
       if($signal["ipAddress"] && $dobFlag)
           $store = true;
                  
       if($store && gettype($callback) == "object"){
           if(!$this->account1->potentialDuplicate)
                $this->account1->potentialDuplicate = true;
           
           $this->account1->threshold += $totalThreshold;
           
           if($this->account1->threshold > PotentialDuplicateAccountChecker::MAXIMUM_THRESHOLD)
               $this->account1->threshold = PotentialDuplicateAccountChecker::MAXIMUM_THRESHOLD;
           
           $callback($totalThreshold);
       }
       
       return $store;
    }
}