<?php
namespace IZON\Admin\Services;

use Exception;
use IZON\DB\OrderBy;
use PDO;

use IZON\DB\Dao;
use IZON\DB\DBObject;

use IZON\Utils\Slug;

/**
 * Base servis pro administraci - obsahuje metody vyuzivane AbstractBaseControllerem
 * Obsahuje metody pro changeSimpleProperty
 * dale metody pro praci s obrazky standartaně v db vazba 1:N
 */
abstract class AbstractBaseService {
    
    /**
     * Standartni dao pro change simple property true false
     * @var Dao
     */
    public $dao;
    
    /**
     * Standartní dao pro manipulaci z obrazky
     * @var Dao
     */
    public $imageDao;
    
    /**
     * @var PDO
     */
    protected $pdo;
    
    // TODO: should habe constructor that sets $dao as almost all methods use it 
    
    /**
     * Slouzi k jednocuche zmene boolean vlastnosti
     * Nejcasteji zmena aktivity zaznamu
     * If is now any active transaction then use this transaction else make own transaction
     * @param integer $objectId - id of updated object
     * @param string $propertyName - name of boolean property (atribute), 
     */
    public function changeSimpleProperty($objectId, $propertyName) {
        $this->checkSetDao();

        try {
            $this->dao->beginTransaction();
            $commit = true;
        }catch(\Exception $e){
            $commit = false;
        }
        $object = $this->dao->load($objectId);
        if ($object != false) {
            $getterGet = 'get' . $propertyName;
            $getterIs = 'is' . $propertyName;
            $setter = 'set' . $propertyName;
            $method = null;

            if (method_exists($object, $getterGet)){
                $method = $getterGet;
            }else if (method_exists($object, $getterIs)){
                $method = $getterIs;
            }else{
                throw new \Exception('Class: '. get_class($object) . ' is not have method ' . $getterIs . ' or ' . $getterGet);
            }

            if (method_exists($object, $setter)) {
                $object->$setter(!$object->$method());
                $this->dao->update($object);
            }else{
                throw new \Exception('Class: '. get_class($object) . ' is nat have method  ' . $setter);
            }
        }

        if($commit) {
            $this->dao->commit();
        }
    }
      
    /**
     * Update pozice objektu
     * Update position of object 
     * @param integer $objId - id of object which you want to update, object have to contain atribute position and method set Position()
     * @param integer $position - position want i set
     */
    public function updateObjectPosition($objId, $position) {
        $this->checkSetDao();
        $obj = $this->dao->load($objId);
        $obj->setPosition($position);
        $this->dao->update($obj);
    }
    
    
    /********************** IMAGE methods ****************************************/
    /*****************************************************************************/
    
    /**
     * Otoci obrazek
     * Turn image which DBobject contain 
     * @param DBObject $image - object, ktery obsahuje obrazek ( DB object musi obsahovat atribut turn a metodu getTurn())
     * @param string $method - nazev metody ktere vrati obrazek z DBObjektu
     */
    public function turnImage(DBObject $image, $method = 'getImage'){
        if($image->getTurn() != 0 && $image->getTurn() != ''){
            $picture = $image->$method();
            $path = ($picture->getFsPath());

            //nacteni
            $mine = $picture->getMineType();
            switch ($mine) {
                case "image/gif":
                    $img = imagecreatefromgif($path);
                    break;
                case "image/jpeg":
                    $img = imagecreatefromjpeg($path);
                    break;
                case "image/png":
                    $img = imagecreatefrompng($path);
                    imagealphablending($img, false);
                    imagesavealpha($img, true);
                    break;
                default:
                    throw new Exception('Unknown image type.');
            }
            //rotace
            $rotate = imagerotate($img, $image->getTurn(), 0); 

            //ulozeni
            switch ($mine) {
                case "image/gif":
                    imagegif($rotate, $path);
                    break;
                case "image/jpeg":
                    imagejpeg($rotate, $path);
                    break;
                case "image/png":
                    imagealphablending( $rotate, false);
                    imagesavealpha($rotate, true);
                    imagepng($rotate, $path);
                    break;
            }

            imagedestroy($img);
            imagedestroy($rotate);
        }
        
    }
    
    /**
     * Metoda pro ukladani std. obrazku - save/update
     * !!  Doporučuji tuto metodu obalit transakci !!!
     * @param DBObject $obj - $object which contain images
     * @param string $methodName - name of method to get images from $obj
     * @param string $connPropName property name that realises connection to $obj
     */
    public function updateImages(DBObject $obj, $methodName = 'getImages', $connPropName = "fkObjectId") {
        $this->checkSetImageDao();
        // zpracovani obrazku
        $images = $this->imageDao->find([$connPropName => $obj->getId()])->listResult();
        $imagesHash = [];
        foreach($images as $image) {
            $imagesHash[$image->getId()] = $image;
        }
        
        foreach($obj->$methodName() as $newImage) {
            //otoceni
            $turn = 0;
            if($newImage->getTurn() != null && $newImage->getTurn() != 0 ){
                $turn = $newImage->getTurn();
            }
            
            $connectionSetterName = "set". ucfirst($connPropName);
            $newImage->$connectionSetterName($obj->getId());
            
            if(isset($imagesHash[$newImage->getId()]) ) {
                $oldImage = $imagesHash[$newImage->getId()];
                $newImage->setImage($oldImage->getImage());
                $this->imageDao->update($newImage);
                unset($imagesHash[$newImage->getId()]);
            } else {
                $this->imageDao->save($newImage);
            }
            
            if($turn != 0){
                $newImage->setTurn($turn);
                $this->turnImage($newImage);
            }
        }
        // odstranit smazane
        foreach($imagesHash as $image) {
            $this->imageDao->delete($image);
        }
    }
    
    /**
     * Smaže všechny obrazky k danému objektu
     *  * !!  Doporučuji tuto metodu obalit transakci !!!
     * @param object $objectId - id objektu ke smazani
     * @param string $connPropName property name that realises connection to $obj
     */
    public function deleteAllImages($objectId, $connPropName = "fkObjectId") {
        $this->checkSetImageDao();
        $images = $this->imageDao->find([$connPropName => $objectId])->listResult();
        foreach ($images as $image) {
            $this->imageDao->delete($image);
        }
    }
   
    /**
     * Return images by fk, in order by column position
     * Method try to order by position on failld return order by default
     * @param integer $fkObjId ID of FK key
     * @param string $tableName Name of images table, Have not be set TODO table name isn't needed any more, mabe remove in future?
     * @return array Array of images
     */
    public function getImagesByFkId($fkObjId, $tableName = NULL, $connPropName = "fkObjectId"){
        $this->checkSetImageDao();
        try{
            $images = $this->imageDao->find([$connPropName => $fkObjId], ['position' => OrderBy::ASC])->listResult();
        }catch (Exception $e){
            $images = $this->imageDao->find([$connPropName => $fkObjId])->listResult();
        }

        return $images;
    }

    /**
     * simple Update (or save) objects in relation 1 to N to object
     * Require every object have getter - getId(), that return id of object
     *
     * Not use to exdens object to update (complicated updates which change more columns than only foreignKey colum)
     * Method is not use transaction
     *
     * Setters of columns(atributtes) have to have same name as column Name (ex. column - fkArticleId, getter - setFkArticleId)
     *
     * @param integer $id id of main object
     * @param Dao $dao dao of ralationship objects
     * @param DBObject[] $objects array of relationship objects
     * @param string $columnName name of column in objects with uset as foreign key, in lower-CamelCase
     * @param callable $beforeDeleteCallable is callled before delete old item
     * @param boolean $delete delete (true), update(false) old item
     */
    public function update1toNObject($id, Dao $dao, array $objects, $columnName = 'fkObjectId', $beforeDeleteCallable = NULL, $delete = true){
        //check input
        $this->checkSetDao();
        if(empty($objects)){
            $objects = [];
        }
        $setter = 'set' . ucfirst($columnName);

        //process update
        $olds = $dao->find([$columnName => $id])->listResult();
        $oldsHash = [];
        foreach($olds as $old){
            $oldsHash[$old->getId()] = $old;
        }
        foreach($objects as $object){
            $object->$setter($id);
            if(isset($oldsHash[$object->getId()])){
                $dao->update($object);
                unset($oldsHash[$object->getId()]);
            }else{
                $dao->save($object);
            }
        }
        foreach($oldsHash as $old){
            if(!empty($beforeDeleteCallable) && is_callable($beforeDeleteCallable)) {
              call_user_func($beforeDeleteCallable, $old);
            }
            if($delete) {
              $dao->delete($old);
            } else {
              $dao->update($old);
            }
        }
    }

    /**
     * simple update (or save) objects in relation N to M
     * Require every object have getter - getId(), that return id of object
     *
     * Not use to exdens object to update - use onli to simple M to N with table that contain only 3 columns (id, fkMandoryId, fkSecondaryId)
     * Method is not use transaction
     *
     * Gettes and setters of columns(atributtes) have to have same name as column Name (ex. column - fkArticleId, getter - getFkArticleId)
     *
     * @param $id integer of mandory object
     * @param Dao $dao dao of relationTable
     * @param DBObject[] $objects array of secondary objects
     * @param string $mandoryColumnName mandory column name in relation table
     * @param string $secondaryColumnName secondary column name in realtion table
     * @param string $relationDomain class name of relation domain
     */
    public function updateNtoNObject($id, Dao $daoRelationShip, array $objects, $mandoryColumnName, $secondaryColumnName, $relationDomain){
        //check input
        $this->checkSetDao();
        if(empty($objects)){
            $objects = [];
        }

        //make setters,getters
        $mandatoryAtributeGetter = 'get'. ucfirst($mandoryColumnName);
        $mandatoryAtributeSetter = 'set'. ucfirst($mandoryColumnName);
        $relationAtributeGetter = 'get'. ucfirst($secondaryColumnName);
        $relationAtributeSetter = 'set'. ucfirst($secondaryColumnName);

        //get olds records
        $olds = $daoRelationShip->find([$mandoryColumnName => $id])->listResult();

        //TODo: check if getters and setter exist

        //process update
        $oldsHash = [];
        foreach($olds as $old){
            $oldsHash[$old->$relationAtributeGetter()] = $old;
        }
        foreach($objects as $object){
            if( isset($oldsHash[$object->getId()])) {
                unset($oldsHash[$object->getId()]);
            } else {
                $relation = new $relationDomain();
                $relation->$mandatoryAtributeSetter($id);
                $relation->$relationAtributeSetter($object->getId());

                $daoRelationShip->save($relation);
            }
        }
        foreach($oldsHash as $old){
            $daoRelationShip->delete($old);
        }
    }
    
    
    /************* PROTECTED and PRIVATE method *******************************/

    /**
     * Make unique slug in table
     * @param string  $name string from which I make slug
     * @param Dao $dao Dao class of table
     * @param string $columnName column where is save slug string
     * @return string Slug
     */
    protected function createUniqueSlugInTable($name, Dao $dao, $columnName = 'slug'){
        $slug = Slug::createSlug($name);
        $res = $dao->find([$columnName => $slug])->listResult();
        if(!empty($res)){
            $num = 0;
            do{
                $num++;
                $res = $dao->find(["slug" => $slug.'-'.$num])->listResult();
            }while(!empty($res));
            $slug = $slug . '-' . $num;
        }

        return $slug;
    }

    /**
     * @deprecated most probbably not used, pdo should not be used and optained in this manner
     * make PDO connection
     */
    protected function makeDBConnection(){
        $dbConn = \IZON\Admin\Config::getValue('db.connection');
        $this->pdo = $dbConn->getPDO();
    }
    
    /**
     * Check if is set atribute dao
     * @throws Exception
     */
    protected function checkSetDao(){
        if(empty($this->dao)){
            throw new \Exception('Atribute dao is not set');
        }
    }
    
    /**
     * Check if is set atribute imageDao
     * @throws Exception
     */
    protected function checkSetImageDao(){
        if(empty($this->imageDao)){
            throw new \Exception('Atribute imageDao is not set');
        }
    }
    
    
    
}
