<?php

namespace apexl\Io\includes;

use apexl\entityCore\Entity as BaseEntity;
use apexl\Io\modules\user\services\currentUser;
use apexl\Io\operators\entityDatabaseOperator;
use apexl\Io\services\globalData;

class Entity extends BaseEntity{
    protected $table;
    protected $dataFields = [];
    protected $readOnlyFields = [];

    //pager fields
    protected $defaultPagerLimit = 25;
    protected $currentPagerOffset = 0;

    /** @var System */
    protected $system;
    /** @var  */
    protected $currentUser;

    public $primaryKey;

    public function __construct($table = '', $primaryKey = 'id')
    {
        $this->table = $table;
        $this->primaryKey = $primaryKey;
        $this->system = System::getInstance();
        parent::__construct();
        $this->setOperator(new entityDatabaseOperator($this->table, $this->primaryKey));

        //If we have the user module, load the current user.
        //@todo change this to a generic authentication check, change user module to extend that auth
        $calledClass = get_called_class();
        if(class_exists('\apexl\Io\modules\user\userModule') && $calledClass != 'apexl\Io\modules\user\entities\userEntity'){
            $user = currentUser::getCurrentUser();
            $this->currentUser = $user ?? null;
        }

        //we only want to run these if we're installed. //Hacky fix for now
        if($this->system->isInstalled()) {
            if(!empty($table)) {
                $this->getEntityDataFields();
            }
        }
        //set default blank object for meta
        $this->data["_metadata"] = (object)[];
    }

    public function getEntityType()
    {
        return __CLASS__;
    }

    public function getEntityName()
    {
        return (new \ReflectionClass($this))->getShortName();
    }

    /**
     * Allow entities to override this and specify a 'nice name'
     * @return string
     */
    public function getNiceName(){
        return $this->getEntityName();
    }

    public function setPagerLimit($limit){
        $this->defaultPagerLimit = $limit;
        return $this;
    }

    public function setCurrentPagerOffset($offset){
        $this->currentPagerOffset = $offset;
        return $this;
    }

    /**
     * By default we want to store the current data object
     * @return $this
     * @throws \Exception
     */
    public function store(){
        //make sure dataFields is set.
        $this->getEntityDataFields();
        //set created / modified stamps if the fields exist
        if(isset($this->dataFields['created']) && !isset($this->data['created'])){
            $this->data['created'] = time();
        }

        if(isset($this->dataFields['created_by']) && !isset($this->data['created_by'])){
            $this->data['created_by'] = $this->currentUser->id ?? 0;
        }

        if(isset($this->dataFields['modified'])){
            $this->data['modified'] = time();
        }

        if(isset($this->dataFields['modified_by'])){
            $this->data['modified_by'] = $this->currentUser->id ?? 0;
        }

        parent::__call('store', [$this->data]);
        return $this;
    }

    /**
     * Method to delete attached entity (or provided id)
     * @param null $id
     * @return $this
     * @throws \Exception
     */
    public function delete($id = null){
        if($id){
            $this->{$this->primaryKey} = $id;
        }
        $delete = Hook::processHook('entityPreDelete', $this, $this->getEntityName());
        if($delete) {
            parent::__call('delete', [$this->{$this->primaryKey}]);
        }
        Hook::processHook('entityPostDelete', $this, $this->getEntityName());
        //deleting? clear the data
        $this->data = [];
        return $this;
    }

    /**
     * Entity load multiple
     * @param array $conditions
     * @param array $orderBy
     * @param null $limit
     * @param null $offset
     * @param bool $asEntities
     * @return array
     * @throws \Exception
     */
    public function loadMultiple($conditions = [], $orderBy = [], $limit = FALSE, $offset = FALSE, $asEntities = TRUE){
        $loaded = [];
        $results = parent::__call('loadMultiple', [$conditions, $orderBy, $limit, $offset]);
        //make sure data is UTF8 Safe, construct entities if required (default)
        if(!empty($results)){
            foreach ($results as $result){
                $data = System::makeUTF8Safe($result);
                $entityType = get_called_class();
                //set entity data (prevents us from having to reload entities)
                $entity = new $entityType();
                //we setData here even if we dont ultimately want the entities,
                //so we can take advantage of the formatting fixes and allow for any custom setData functionality
                $entity->setData($data);
                //build an array of loaded entities, or just get the data if that's what we want.
                $entity = Hook::processHook('entityPostLoadMultiple', $entity, $entity->getEntityName());
                //TN do not key by ID as we need to preserve order with a 0 key'd array. This is to allow JS consumers to maintain the order.
                if ($asEntities) {
                    $loaded[] = $entity;
                } else {
                    $loaded[] = $entity->getData();
                }
            }
        }
        return $loaded;
    }

    /**
     * Function to get all results, paged
     * @param null $params
     * @param array $filters
     * @param array $orderBy
     * @return array
     */
    public function loadByPage($params = NULL, $filters = [], $orderBy = []){
        $this->setPagerLimit(($params['limit'] ?? FALSE));
        $this->setCurrentPagerOffset(($params['offset'] ?? 0));
        //if we have a page param set, calculate offset on the fly.
        if($page = $params['page'] ?? FALSE){
            //if we have a page, assume we need a limit, if its 0 set a default.
            $this->defaultPagerLimit = $this->defaultPagerLimit === 0 ? 25 : $this->defaultPagerLimit;
            $this->currentPagerOffset = ($this->defaultPagerLimit * $page) - $this->defaultPagerLimit;
        }

        $totalResults = $this->totalEntities($filters);
        $totalPages = $this->defaultPagerLimit == 0 ? 1 : ceil (($totalResults/$this->defaultPagerLimit));

        $data = $this->loadMultiple($filters, $orderBy, $this->defaultPagerLimit, $this->currentPagerOffset, FALSE);

        return ['data' => $data, 'totalData' => $totalResults, 'totalPages' => $totalPages, 'totalResults' => count($data)];
    }

    /**
     * Function to get the fields for the entity (from the primary table).
     * Allows us to check what data is storable and strip data that isn't.
     * Will only return fields not specified as 'read only' in the entity settings.
     * @see the $readOnlyFields property.
     */
    public function getEntityDataFields(){
        if(empty($this->dataFields)) {
            $this->dataFields = $this->operator->getTableColumns();
        }
    }

    /**
     * Add metadata to the object, this will not be stored in the database unless manually done
     * @param $name
     * @param $data
     * @return $this
     */
    public function addMetadata($name, $data){
        $this->data["_metadata"]->$name = $data;
        return $this;
    }

    /**
     * Get data from metadata store, if it exists. Null otherwise
     * @param $name
     * @return mixed
     */
    public function getMetadata($name){
        return $this->data["_metadata"]->$name ?? NULL;
    }

    /**
     * Function to flag certain data fields as read only (data changed in these fields will not be stored against the original loaded entity)
     * @param array $fields
     */
    public function setReadOnlyFields(array $fields){
        $this->readOnlyFields = $fields;
    }

    /**
     * @param array $filters
     * @return array|mixed
     * @throws \Exception
     */
    public function totalEntities($filters = []){
        return parent::__call('totalEntities', [$filters]);
    }
}