<?php

include_once("demosettings.php");
include_once ("sqlTable.php");
include_once 'write_log.php';
class PredictiveProcessClass{
    private $mysqli;
    private $username;
    private $isAdmin;
    private $type;
    private $total_docs;
    private $total_predictive_docs;
    private $saved_search_id;
    private $predictive_search_docid;
    
    function __construct($mysqli, $type, $saved_search_id, $predictive_search_docid, $username, $isAdmin){
        $this->mysqli = $mysqli;
        $this->username = $username;
        $this->isAdmin = $isAdmin;
        $this->type = $type;
        $this->saved_search_id = $saved_search_id;    
        $this->predictive_search_docid = $predictive_search_docid;
        $this->total_docs = !empty($this->predictive_search_docid) ? 1 : self::getNumDocsFromSavedSearch($this->saved_search_id);
        
    }
    
    /**
     * getNumDocsFromSavedSearch - gets the # of docs in the savedSearchId docs corpus
     */
    function getNumDocsFromSavedSearch($id) : int{
        $sql = "SELECT count(docid) as c FROM savedsearchid WHERE id=$id";
        $res = sqlQuery($this->mysqli, $sql);
        if(($row = $res->fetch_object()) != false){
            return intval($row->c);
        }
        return 0;
    }
    
    /**
     * search_process - its the main function
     */
    function search_process($mysqli, $total_files){
        //0 - get the number of docs in savedsearchid corpus
        
        //1 - get List of features (all concepts/instances on site)
        $listClass = new GetListOfFeaturesAll($mysqli, $this->isAdmin, $this->username);
        $feature_list = $listClass->getFeaturesList();
        
        //2 - get the model fro the search docids
        
        switch ($this->type)
        {
            case 'idf':
                $modelClass = new GetIdfModelClass($mysqli, $this->saved_search_id, $this->predictive_search_docid, $feature_list, $this->total_docs);
                break;
            default:
                $modelClass = new GetModelClass($mysqli, $this->saved_search_id, $this->predictive_search_docid, $feature_list, $this->total_docs);
                
        }  
        $model = $modelClass->getModelFromCorupus();
        
        //3 - decide the score/distance class
        $score_class = new EuclideanDistance();
        
        //4 - gets the docuemnts that fits the model
        $docsClass = new GetDocsClass($mysqli, $this->username, $this->isAdmin, $model, $modelClass, $this->saved_search_id, $feature_list, $this->total_docs, $score_class, $total_files);
        
        $docsClass->getDocsInRange();
        $this->total_predictive_docs = self::getNumDocsFromSavedSearch($this->saved_search_id);
    }
    
    function getPredictive_search_id(){
        return $this->saved_search_id;
    }
    
    function getTotalPredictiveDocs(){
        return $this->total_predictive_docs;
    }
    
    /**
     * filter_limit($precent) - limit the results to the % closets to the model
     * deleting it from the savedsearchid
     * @return number
     */
    function filter_limit($precent){
   
        //first count the docid in the predictive coding (now it's all documents but might change)
        $sql = "SELECT count(docid) AS c FROM savedsearchid WHERE id = $this->saved_search_id";
        $res = sqlQuery($this->mysqli, $sql);
        $row = $res->fetch_object();
        $count = $row->c;
        
        //calculate the index with the limit score
        $limit_index = ceil(($count/100)*$precent);
        $sql = "SELECT score FROM (
                SELECT docid,score FROM savedsearchid WHERE id = $this->saved_search_id ORDER BY score ASC
                ) AS T LIMIT $limit_index,1";
        $res = sqlQuery($this->mysqli, $sql);
        $row = $res->fetch_object();
        $limit_score = $row->score;
        
        //delete the elements with higer score than limit_score
        $sql = "DELETE FROM savedsearchid WHERE id=$this->saved_search_id AND score > $limit_score";
        sqlQuery($this->mysqli, $sql);
    }

}

//**********************************************************************************************************************************//
//**********************************************************GetListOfFeatures interface*********************************************//
//**********************************************************************************************************************************//
interface GetListOfFeatures
{
    public function getFeaturesList();
}

trait getListTrait{

    function getSql($username, $isAdmin, $binary = null) {
        $user_sql = (!$isAdmin) ? " username='$username'" : " TRUE";   
        if($binary){
            return "SELECT BINARY ontname as n, type as t, COUNT(docid) as d, SUM(occurrences) as o FROM concept WHERE $user_sql GROUP BY n";
        }
        return "SELECT ontname as n, type as t, COUNT(docid) as d, SUM(occurrences) as o FROM concept WHERE $user_sql GROUP BY n";
    }
}

//get concept list by Instances
class GetListOfFeaturesOnlyInstances implements GetListOfFeatures
{
    use getListTrait;
    private $mysqli;
    private $isAdmin;
    private $username;
    private $igonre_type_arr = array('Person-object','Organizational-identity','Place-object');
    
    
    function __construct($mysqli, $isAdmin, $username){
        $this->mysqli = $mysqli;
        $this->isAdmin = $isAdmin;
        $this->username = $username;
    }
    
    public function getFeaturesList()
    {
        $vector = array();
        $res = sqlQuery($this->mysqli, self::getSql($this->username, $this->isAdmin));
        while (($row = $res->fetch_object()) != false){
            if(!in_array($row->t, $this->igonre_type_arr) && ctype_upper($row->n{0})){
                continue;
            }
            $vector[$row->n]['occ'] = 0;
            $vector[$row->n]['idf'] = intval($row->d);
        }
        return $vector;
    }
}

//get concept list by concepts
class GetListOfFeaturesOnlyConcpets implements GetListOfFeatures
{
    use getListTrait;
    private $mysqli;
    private $isAdmin;
    private $username;
    
    function __construct($mysqli, $isAdmin, $username){
        $this->mysqli = $mysqli;
        $this->isAdmin = $isAdmin;
        $this->username = $username;
    }
    
    public function getFeaturesList()
    {
        $vector = array();
        $res = sqlQuery($this->mysqli, self::getSql($this->username, $this->isAdmin));
        while (($row = $res->fetch_object()) != false){
            if(!ctype_upper($row->n{0})){
                continue;
            }
            $vector[$row->n]['occ'] = intval($row->o);
            $vector[$row->n]['idf'] = intval($row->d);        
        }
        return $vector;
    }
}

//get concept list by all
class GetListOfFeaturesAll implements GetListOfFeatures
{
    use getListTrait;
    private $mysqli;
    private $isAdmin;
    private $username;
    
    function __construct($mysqli, $isAdmin, $username){
        $this->mysqli = $mysqli;
        $this->isAdmin = $isAdmin;
        $this->username = $username;
    }
    
    public function getFeaturesList()
    {
        $vector = array();
        $sql = self::getSql($this->username, $this->isAdmin, true);
        $res = sqlQuery($this->mysqli, $sql);
        while (($row = $res->fetch_object()) != false){
            $vector[$row->n]['occ'] = intval($row->o);
            $vector[$row->n]['idf'] = intval($row->d);
        }
        return $vector;
    }
}
//**********************************************************************************************************************************//
//**********************************************************END GetListOfFeatures interface*****************************************//
//**********************************************************************************************************************************//

//**********************************************************************************************************************************//
//**********************************************************FetchModel interface****************************************************//
//**********************************************************************************************************************************//
interface FetchModel
{
    public function getModelFromCorupus();
    public function getSingleDocModel($id);
    public function accumlateVector($id);
}

/**
 * GetModelClass
 */
class GetModelClass implements FetchModel
{
    private $mysqli;
    private $features_vector;
    private $savedSearchId;
    private $predictive_search_docid;
    private $count_docs;
    
    function __construct($mysqli, $saved_search_id, $predictive_search_docid, $features_vector, $num_docs){
        $this->mysqli = $mysqli;
        $this->features_vector = $features_vector;
        $this->savedSearchId = $saved_search_id;
        $this->predictive_search_docid = $predictive_search_docid;
        $this->count_docs = $num_docs;;
    }
 
    public function getModelFromCorupus($id = null){        
        
        $vector = self::accumlateVector(); //sum up to a vector all the occurences
        
        //gets the avarage of data in vectors - if its array of docids
        foreach($vector as &$element){
            $element = $element/$this->count_docs;
        }
        
        return $vector;
        
    }
    
    public function accumlateVector($id = null){
        $keys = array_keys($this->features_vector);
        $vector = array_fill_keys($keys,0);
        
        if(empty($id)){            
            $savedSearchIdStr =  empty($this->predictive_search_docid) ? " IN(SELECT docid FROM savedsearchid WHERE id=$this->savedSearchId)" : " = $this->predictive_search_docid";
        }
        else{
            $savedSearchIdStr = " = $id";
        }
        
        $sql = "SELECT ontname as n, docid as d, occurrences as o FROM concept WHERE docid  $savedSearchIdStr";
        $res = sqlQuery($this->mysqli, $sql);
        
        while (($row = $res->fetch_object()) != false){
            if(!isset($vector[$row->n])){
                continue;
            }
            $vector[$row->n] += intval($row->o);
        }
        ksort($vector);
        return $vector;
    }
    /**
     * {@inheritDoc}
     * @see FetchModel::getSingleDocModel()
     */
    public function getSingleDocModel($vector)
    {
        return  $vector;
        
    }
}

/**
 * GetIdfModelClass
 */
 class GetIdfModelClass implements FetchModel
{
    private $mysqli;
    private $features_vector;
    private $savedSearchId;
    private $predictive_search_docid;
    private $count_docs;
    
    function __construct($mysqli, $saved_search_id, $predictive_search_docid, $features_vector, $num_docs){
        $this->mysqli = $mysqli;
        $this->features_vector = $features_vector;
        $this->savedSearchId = $saved_search_id;
        $this->predictive_search_docid = $predictive_search_docid;
        $this->count_docs = $num_docs;;
    }
    
    public function getModelFromCorupus(){
        
        $vector = self::accumlateVector(); //sum up to a vector all the occurences
        
        //gets the avarage of data in vectors - if its array of docids
        foreach($vector as &$element){
            $element = $element/$this->count_docs;
        }
        
        $vector = self::getSingleDocModel($vector);
        
        return $vector;
        
    }
    
    public function getSingleDocModel($vector){
        
        $tf_idf = array();
        $total_sum = array_sum($vector);
        
        foreach($vector as $k=>$v){
            $curr_idf = log($this->count_docs/$this->features_vector[$k]['idf']);
            if($curr_idf==0){
                continue;
            }
            $curr_tf = ($total_sum !== 0) ? ($v/$total_sum) : 0;
            
            $tf_idf[$k] = $curr_tf*$curr_idf;
        }
        return $tf_idf;
    }
    
    public function accumlateVector($ids = null){
        $total_vector = array(); //only for ids not null
        $keys = array_keys($this->features_vector);
        $vector = array_fill_keys($keys,0);
        
        if(empty($ids)){
            $savedSearchIdStr = !empty($this->predictive_search_docid) ? "=$this->predictive_search_docid" : "IN(SELECT docid FROM savedsearchid WHERE id=$this->savedSearchId)";
        }
        else{
            $savedSearchIdStr = "IN(".implode(',', array_values($ids)).")";
        }
        
        $sql = "SELECT ontname as n, docid as d, occurrences as o FROM concept WHERE docid $savedSearchIdStr";
        $res = sqlQuery($this->mysqli, $sql);
        while (($row = $res->fetch_object()) != false){
            if(!isset($vector[$row->n])){
                continue;
            }
            if(!empty($ids)){
                if(!isset($total_vector[$row->d])){
                    $total_vector[$row->d] = $vector;
                }
                $total_vector[$row->d][$row->n] += intval($row->o); 
            }else{
                $vector[$row->n] += intval($row->o);
            }
        }
        if(!empty($ids)){
            return $total_vector;
            
        }
        ksort($vector);
        
        return $vector;
    }
    
}

//**********************************************************************************************************************************//
//**********************************************************END FetchModel interface************************************************//
//**********************************************************************************************************************************//

//**********************************************************************************************************************************//
//**********************************************************FetchDocs interface*****************************************************//
//**********************************************************************************************************************************//
interface FetchDocs
{
    public function getDocsInRange();
}

//get regular model class
class GetDocsClass implements FetchDocs
{
    
    private $mysqli;
    private $features_vector;
    private $savedSearchId;
    private $count_docs;
    private $model;
    private $username;
    private $isAdmin;
    private $model_class;
    
    function __construct($mysqli, $username, $isAdmin, $model, $model_class, $saved_search_id, $features_vector, $num_docs, $score_class, $total_files){
        $this->mysqli = $mysqli;
        $this->username = $username;
        $this->isAdmin = $isAdmin;
        $this->model = $model;
        $this->model_class = $model_class;
        $this->features_vector = $features_vector;
        $this->savedSearchId = $saved_search_id;
        $this->count_docs = $num_docs;
        $this->score_class = $score_class;
        $this->total_files = $total_files;
    }


    /**
     *  getDocsInRange() - fetch docs that fits the model 
     * @return $ret_arr - docs id that are in the range of the threshold from the tested model (from $this->centroid)
     */
    public function getDocsInRange(){   
        $batch_limit = 1000;
        $values = "";
        $batch_index = 0;
        //get id of Predictive Search (saved in the table savedSearch - only one record of it)
        $res_sql = sqlQuery($this->mysqli, "DELETE FROM savedsearchid WHERE id=$this->savedSearchId");
        
        //gets arr of all files ids
        //TODO: change this SQL to search --> outputs sql that fetch user's all files
        $user_sql = (!$this->isAdmin) ? " username='$this->username'" : " TRUE";
        
        $counter_batch = 0;
        $sql_all_files = "SELECT distinct id FROM files WHERE $user_sql";
        
        
        while($counter_batch < $this->total_files){
            $sql_batch = "SELECT id FROM ($sql_all_files) as t1 LIMIT $counter_batch, $batch_limit";
            $values = "";
        
            $res = sqlQuery($this->mysqli, $sql_batch);
            
            $counter_batch += $batch_limit;
            
            if (($doc_arr = $res->fetch_all()) != false){
                $doc_arr = array_merge(...$doc_arr);
                $total_vector = $this->model_class->accumlateVector($doc_arr);
                foreach($total_vector as $docid=>$curr_vector){
                    write_to_log("INFO", "calculated document num: $docid");
                    
                    $curr_model = $this->model_class->getSingleDocModel($curr_vector);
                    $diff = $this->score_class->getDistance($curr_model, $this->model); //gets the distance between the doc centroid to model centroid
                
                    //update savedsearchid with (id,docid,score)
                    $values .= "($this->savedSearchId,$docid,$diff),";
                }
               
            }
            
            $values = rtrim($values, ',');
            $res_sql = sqlQuery($this->mysqli, "INSERT INTO savedsearchid (id,docid,score) VALUES $values ON DUPLICATE KEY UPDATE id=id");
        }
    }
    

}
//**********************************************************************************************************************************//
//**********************************************************END FetchDocs interface*************************************************//
//**********************************************************************************************************************************//


//**********************************************************************************************************************************//
//**************************************************************Distance interface**************************************************//
//**********************************************************************************************************************************//
interface Distance
{
    public function getDistance(array $vector1, array $vector2) : float;
}

//get regular model class
class EuclideanDistance implements Distance
{
     /**
     * distance($vector1, $vector2) - distance between 2 vectors
     * @param  $vector1
     * @param  $vector2
     * @return distance between vector 1 to 2
     */
    
    public function getDistance(array $vector1, array $vector2) : float
    {
        $n = count($vector1);
        $m = count($vector2);
        
        $sum = 0;
        foreach ($vector1 as $key => $value) {
            $sum += ($vector1[$key] - $vector2[$key]) * ($vector1[$key] - $vector2[$key]);
        }
        
        return sqrt($sum);
    }
}

//**********************************************************************************************************************************//
//**********************************************************END Distance interface**************************************************//
//**********************************************************************************************************************************//