<?php
/**
 * 
 * @author Anton
 *
 */
class EnhancedCommonChecker {
    
    /**
     * 
     * @var String
     */
    public $focused;
    
    /**
     * 
     * @var String
     */
    public $source;
    
    private $inverted = null;
    
    private $matchedTokens = [];
    private $diffTokens = [];
    
    public function __construct(){
        
    }
    
    public function init() : void {
        $this -> inverted();
    }
    
    /**
     * 
     * @param String $focused
     * @param String $source
     * @return EnhancedCommonChecker
     */
    public function calculate(String $focused, String $source, bool $includeInvert = true) : EnhancedCommonChecker {
        if($this->inverted != null && $includeInvert){
            $this->inverted->setSubjects($source, $focused);
            $this->inverted->matchingProcess();
        }
        
        $this->setSubjects($focused, $source);
        $this->matchingProcess();
        
        return $this;
    }
    
    /**
     * 
     * @param unknown $focused
     * @param unknown $source
     */
    private function setSubjects($focused, $source) : void {
        $this -> focused = $focused;
        $this -> source = $source;
    }
    
    /**
     * 
     * collation and investigation
     * 
     */
    private function collateMatches() : void {
        $mtokens = [];
        
        $focused = strtolower($this -> focused);
        $source = strtolower($this -> source);
        
        $lastIndex = strlen($source) - 1;
        $previousIndex = -1;
        
        for($i=0; $i < strlen($source); $i++){
            
            $toSearch = substr($source, $i);
            $sbuilt = "";
            
            for($k=0; $k < strlen($toSearch); $k++){
                $sbuilt .= $toSearch[$k];
                $progress = $i + $k;
                
                $a = substr($sbuilt, 0, -1);
                $builtpos = strpos($focused, $sbuilt);
                
                if($builtpos===false && !empty($a) && strpos($focused, $a) === false) break;
                
                if($builtpos===false || $progress === (strlen($source) - 1)){
                    $previousPotentialCommon = substr($sbuilt, 0, strlen($sbuilt) -
                        ($builtpos!==false && $progress == (strlen($source) - 1) ? 0 : 1));
                    
                    $ppclen = strlen($previousPotentialCommon);
                    
                    if((!empty($previousPotentialCommon) && strpos($focused, $previousPotentialCommon) !== false) && $ppclen > 1 || ($i == $lastIndex)){
                        
                        if($i == $lastIndex && (strlen($focused) >= 2 && strlen($source) >= 2)){
                            if($focused[0] != $source[0] || $focused[1] != $source[1]) continue;
                        }
                        
                        $index = (!empty($previousPotentialCommon) ? strpos($focused, $previousPotentialCommon) : false);

                        while($index !== false){
                            if($ppclen == 1 && $index != (strlen($focused) - 1)){
                                $index = strpos($focused, $previousPotentialCommon, $index + 1);
                                continue;
                            }
                            
                            if(isset($mtokens[$previousIndex])){
                                $previousToken = $mtokens[$previousIndex];
                                
                                $ppcIndexEnd = $index + ($ppclen - 1);
                                $ptIndexEnd = $previousIndex + (strlen($previousToken["phrase"]) - 1);
                                
                                if($index > $previousIndex && $ppcIndexEnd <= $ptIndexEnd){
                                    $index = strpos($focused, $previousPotentialCommon, $index + 1);
                                    continue;
                                }
                            }
                        
                            if($previousIndex !== -1){
                                if(isset($mtokens[$previousIndex][$index])){
                                    if($mtokens[$previousIndex][$index] == $previousPotentialCommon[0]){
                                        $index = strpos($focused, $previousPotentialCommon, $index + 1);
                                        continue;
                                    }
                                }
                            }
                            
                            $stkens = array_values(array_map(function($a){
                                return $a["sourceIndex"];
                            }, $mtokens));
                            
                            if(!in_array($i, $stkens)){                                
                                if(!isset($mtokens[$index])){
                                    if($previousIndex !== -1){
                                        $prevmaxlen = ($mtokens[$previousIndex]["sourceIndex"] + strlen($mtokens[$previousIndex]["phrase"])) - 1;
                                        if($i >= $mtokens[$previousIndex]["sourceIndex"] && $i <= $prevmaxlen){
                                            $index = strpos($focused, $previousPotentialCommon, $index + 1);
                                            continue;
                                        }
                                    }
                                    
                                    $previousIndex = $index;
                                   
                                    $mtokens[$index] = [
                                        "sourceIndex" => $i,
                                        "phrase" => $previousPotentialCommon
                                    ];
                                }
                            }
                          
                            $index = strpos($focused, $previousPotentialCommon, $index + 1);
                        }
                    }
                }
            }
        }
        
        $this->matchedTokens = $mtokens;
    }
    
    /**
     * 
     * collation and investigation
     * 
     */
    private function collateDifferences() : void {
        /*
         * this will count and collate the differences between two strings.
         */
        $dtokens = [];
        $indices = $this -> charIndices(true);
        
        $cbuild = "";
        $skip = -1;
        
        for($i=0; $i < count($indices); $i++){
            $nextelem =(isset($indices[$i + 1]) ? $indices[$i + 1] : -1);
            $missed = abs($indices[$i] - $nextelem) - 1;
            
            if($indices[$i] == strlen($this -> focused) - 1){
                $missed = 1;
            }
            
            if($missed > 0){
                if(strlen($cbuild) > 0){
                    $start = $indices[$i] - (strlen($cbuild) - 1);
                    $dtokens[$start] = strtolower($cbuild);
                    $cbuild = "";
                }else{
                    if($this -> focused[$indices[$i]] == " "){
                        continue;
                    }
                    $dtokens[$indices[$i]] = strtolower($this -> focused[$indices[$i]]);
                }
            }else{
                if($nextelem < 0){
                    continue;
                }
                
                $cbuild .= ($indices[$i] != $skip ? $this -> focused[$indices[$i]] : "")."{$this -> focused[$nextelem]}";
                $skip = $nextelem;
            }
        }
        
        $this -> diffTokens = $dtokens;
    }
    
    private function matchingProcess() : void {
        $this -> collateMatches();
        $this -> collateDifferences();
    }
    
    /**
     * 
     * returns the same object which favors the 'source' analyzation 
     * 
     * @return EnhancedCommonChecker
     */
    public function inverted() : EnhancedCommonChecker {
        if($this -> inverted == null)
            $this -> inverted = new EnhancedCommonChecker();
        return $this -> inverted;
    }
    
    /**
     * 
     * @return string[]
     */
    public function commons() : Array {
        return $this -> matchedTokens;
    }
    
    /**
     * 
     * @param bool $unmatched
     * @return array
     */
    public function charIndices(bool $unmatched = false) : Array {
        $cmap = $this -> commons();
        
        $indices = [];
        foreach($cmap as $index => $value){
            for($k = $index; $k <= ($index + (strlen($value["phrase"]) - 1)); $k++){
                $indices[] = $k;
            }
        }
        
        if($unmatched){
            $temp = [];
            
            for($i = 0; $i < strlen($this -> focused); $i++){
                if (in_array($i, $indices)) {
                    continue;
                } else {
                    $temp[] = $i;
                }
            }
            $indices = $temp;
        }
        
        $indices = array_unique($indices);
        return $indices;
    }
    
    /**
     *
     * @return string[]|String[]
     */
    public function diffstring() : Array {
        return $this -> diffTokens;
    }
    
    /**
     *
     * @return number
     */
    public function difference() : int {
        $map = array_map(function($element){
            return $element["phrase"];
        }, $this -> commons($this -> focused, $this -> source));
        
        return strlen($this -> focused) - strlen(implode($map));
    }
    
    /**
     *
     * @return number
     */
    public function commonlen() : int {
        return strlen($this -> focused) - $this -> difference($this -> focused, $this -> source);
    }
    
    /**
     * 
     * 
     * @param bool $sourcePerspective
     * @return mixed
     */
    public function details(bool $sourcePerspective = false) : String {
        /**
         * 
         * @var String $subject
         */
        $subject = $this -> focused;
        
        /**
         * 
         * @var EnhancedCommonChecker $object
         */
        $object = $this;
        
        if($sourcePerspective){
            $subject = $this -> source;
            $object = $this -> inverted;
        }
        
        $data = [
            "phrase" => $subject,
            "matched" => $object->commons(),
            "unmatched" => $object->diffstring(),
            "difflen" => $object->difference(),
            "indices" => $object->charIndices()
        ];
        
        return print_r($data, true);
    }
}