<?php

namespace ModulesGarden\ProxmoxAddon\Core\CommandLine;

use ModulesGarden\ProxmoxAddon\Core\FileReader\Validator;
use ModulesGarden\ProxmoxAddon\Core\ModuleConstants;
use ModulesGarden\ProxmoxAddon\Core\DependencyInjection;
use ModulesGarden\ProxmoxAddon\Core\FileReader\File;
use ModulesGarden\ProxmoxAddon\Core\CommandLine\ReaderCronTask;
use ModulesGarden\ProxmoxAddon\Core\Models\CronTabs;

/**
 * Description of CronManager
 *
 * @author Rafał
 */
class CronManager {
    
    const AVAILABLE_MODES = ['cli', 'cgi-fcgi'];
    
    protected $arguments = [];
    
    protected $appCronPath = '';
    
    protected $coreCronPath = '';
    
    protected $parentUId;
    
    protected $parentGId;
    
    protected $cronPids = '';
    
    protected $isParent = true;
    
    protected $parentId;
    
    protected $childId;

    /**
     * @var Validator
     */
    protected $fileValidator;
    
    public function __construct(Validator $fileValidator) {
        $this->fileValidator = $fileValidator;
        $this->appCronPath   = ModuleConstants::getModuleRootDir() . DS . 'app' . DS . 'Cron' . DS;
        $this->coreCronPath  = ModuleConstants::getModuleRootDir() . DS . 'core' . DS . 'Cron' . DS;
        $this->cronPidsPath  = ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS;
        
        if($this->fileValidator->isExist($this->cronPidsPath) === false)
        {
            mkdir($this->cronPidsPath);
            File::setPermission($this->cronPidsPath);
        }

        if (self::isThread())
        {
            $this->appCronPath  .= "Thread" . DS;
            $this->coreCronPath .= "Thread" . DS;
        }
        else 
        {
            $this->appCronPath  .= "WithoutThread" . DS;
            $this->coreCronPath .= "WithoutThread" . DS;
        }
        
        $this->parentGId = getmygid();
        $this->parentUId = getmyuid();
    }
    
    public function setArgv($arguments)
    {
        $this->arguments = $arguments;
        
        return $this;
    }
    
    public function execute()
    {
        $this->loadArguments();
        
        if($this->isInCliMode() === false)
        {
            return $this;
        }
        if (count($this->arguments) == 0)
        {
            $this->arguments = ReaderCronTask::get();
        }
        
        $this->loadDefaultCronManager();
        foreach ($this->arguments as $className)
        {
            $className = trim(trim($className, '--'), '-');
            $class = ModuleConstants::getRootNamespace() . "\\";
            if ($this->fileValidator->isExist($this->appCronPath . $className . ".php"))
            {
                $class .= "App\Cron\\" . $this->getType() . "\\" . $className;
                $this->runTask($class, $className);
            }
            elseif ($this->fileValidator->isExist($this->coreCronPath . $className . ".php"))
            {
                $class .= "Core\Cron\\" . $this->getType() . "\\" . $className;
                $this->runTask($class, $className);
            }
        }
        
        return $this;
    }
    
    protected function runTask($class, $className)
    {
        if ($this->classExist($class) === true && $this->isCronRunning($className) === false)
        {
            if ($this->beforeRunTask($className) === false)
            {
                return;
            }
            
            $instant = DependencyInjection::create($class, null, (class_exists(\Thread::class)?false:true));
            $instant->setCronManager($this);
            $instant->setClassName($className);
            if (self::isThread() === false)
            {
                $instant->ischild(!$this->isParent)
                        ->setChildId($this->childId)
                        ->setParentId($this->parentId)
                        ->setParentGroupId($this->parentGId)
                        ->setParentUserId($this->parentUId);

            }
            $instant->start();
            unset($instant);
            $this->afterRunTask($className);
        }
    }
    
    protected function getType()
    {
        return (self::isThread()?'Thread':'WithoutThread');
    }
    
    public static function isThread()
    {
        return class_exists(\Thread::class);
    }
    
    protected function loadArguments()
    {
        $deleteClass = [];
        foreach ($this->arguments as $key => $name) {
            if (strpos($name, 'cron.php') !== false)
            {
                $deleteClass[] = $key; 
            }
            elseif ($name === '--parent')
            {
                $this->parentId = $this->arguments[$key + 1];
                $this->isParent = false;
                $deleteClass[] = $key; 
                $deleteClass[] = $key + 1; 
            }
            elseif ($name === '--child')
            {
                $this->childId = $this->arguments[$key + 1];
                $this->isParent = false;
                $deleteClass[] = $key; 
                $deleteClass[] = $key + 1; 
            }
            elseif ($name === '--parentuid')
            {
                $this->parentUId  = $this->arguments[$key + 1];
                $deleteClass[]  = $key;
                $deleteClass[]  = $key + 1;
            }
            elseif ($name === '--parentgid')
            {
                $this->parentGId  = $this->arguments[$key + 1];
                $deleteClass[]  = $key;
                $deleteClass[]  = $key + 1;
            }
        }
        
        foreach ($deleteClass as $clasKey)
        {
            unset($this->arguments[$clasKey]);
        }
        
        return $this;
    }
    
    protected function classExist($name)
    {
        return class_exists($name);
    }
    
    public static function updatePids($name, $id = '')
    {
        touch(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
    }
    
    public static function removePids($name, $id = '')
    {
        unlink(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
    }
    
    public static function existPids($name, $id = '')
    {
        return file_exists(ModuleConstants::getModuleRootDir() . DS . 'storage' . DS . 'crons' . DS . $name. 'Pid' . $id . '.php');
    }


    public function createPids($file)
    {
        return file_put_contents($file, '<?php die(); '.getmypid());
    }
    
    public function isChildRunning($name, $id)
    {
        $file = $this->cronPidsPath . $name. 'Pid' . $id . '.php';
        
        if($this->fileValidator->isExist($file) && filemtime($file) > time() - 7200)
        {
            return true;
        }
        return false;
    }
    
    private function isCronRunning($name)
    {
        if ($this->isParent === false)
        {
            $file = $this->cronPidsPath . $name. 'Pid' . $this->childId . '.php';
        
            if($this->fileValidator->isExist($file) && filemtime($file) > time() - 7200)
            {
                return true;
            }

            if($this->createPids($file) === false)
            {
                return true;
            }
        }
        
        return false;
        
        /**
        if(PHP_OS == "Linux")
        {
            $out = null;
            exec("ps aux | grep 'php -q.*modules/addons/AdvancedBilling/cron/cron.php$' | grep -v 'grep'", $out);
            if(count($out) > 1)
            {
                return true;
            }
        }
        **/
    }

    
    private function isInCliMode()
    { 
        if (in_array(php_sapi_name(), self::AVAILABLE_MODES)) 
        {
            return true;
        } 
        else 
        {
            return false;
        }
    }
    
    protected function loadDefaultCronManager()
    {
        $defaultCron = CronTabs::ifDefaultCronManager()->first();
        if ($this->isParent && $defaultCron && $defaultCron->status != 'start')
        {
            CronTabs::ifDefaultCronManager()->updateStatus('start');
            unset($defaultCron);
        }
        elseif ($this->isParent && !$defaultCron)
        {
            CronTabs::create([
                'name'   => CronTabs::DEFAULT_CRON_MANAGER_NAME,
                'status' => 'start',
                'params' => [],
                'errors' => []
            ]);
        }
        
        return $this;
    }
    
    protected function beforeRunTask($className)
    {
        if ($this->isParent)
        {
            if (CronTabs::ifDefaultCronManager()->getQuery()->first()->status == 'stop')
            {
                exit;
            }
            $cronTask = CronTabs::ifName($className)->getQuery()->first();
            if ($cronTask && $cronTask->status != 'stop')
            {
                return false;
            }

            $params = [
                'parentId'  => getmypid(),
                'parentUId' => $this->parentUId,
                'parentGId' => $this->parentGId
            ];
            if ($cronTask)
            {
                CronTabs::ifName($className)->updateStatus('start', $params);
            }
            else
            {
                CronTabs::create([
                    'name'   => $className,
                    'status' => 'start',
                    'params' => $params,
                    'errors' => []
                ]);
            }

            usleep(500000);
            $cronTask = (new CronTabs())->ifName($className)->getQuery()->first();
            if ($this->isParent && json_decode($cronTask->params)->parentId != getmypid())
            {
                return false;
            }
        }
        
        return true;
    }
    
    protected function afterRunTask($className)
    {
        if ($this->isParent)
        {
            CronTabs::ifName($className)->updateStatus();
        }
        
        return $this;
    }
}
