<?php

/**
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2017 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
 *
 */

namespace oat\taoTaskQueue\model\Worker;

use Exception;
use oat\oatbox\log\LoggerAwareTrait;
use oat\oatbox\service\ConfigurableService;
use React\ChildProcess\Process;

class WorkerProcessManager extends ConfigurableService
{
    use LoggerAwareTrait;

    const SERVICE_ID = 'taoTaskQueue/WorkerProcessManager';

    const OPTION_TASK_COMMAND = 'task_command';

    /** @var Process[] */
    private $processes = [];

    /** @var integer */
    private $limitOfCpu;

    /** @var integer */
    private $limitOfMemory;

    /**
     * @param Process $process
     */
    public function addProcess(Process $process)
    {
        $pid = $process->getPid();

        $process->stdout->on('data', function ($status) use ($pid) {
            $this->logInfo('Process: ' . $pid . ' status:' . $status);
        });

        $process->stdout->on('end', function () use ($pid) {
            $this->logInfo('Process: ' . $pid . ' ended');
        });

        $process->stdout->on('error', function (Exception $e) use ($pid) {
            $this->logError('Process: ' . $pid . ' error. ' . $e->getMessage());
        });

        $process->stdout->on('close', function () use ($pid) {
            $this->logInfo('Process: ' . $pid . ' closed.');

            unset($this->processes[$pid]);
        });

        $process->stdin->end($data = null);

        $this->processes[$pid] = $process;
    }

    /**
     * @return bool
     */
    public function canRun()
    {
        $this->logInfo('No of process workers running: ' . count($this->processes));

        $memoryUsage = $this->getMemoryUsage();
        $cpuUsage    = $this->getCpuUsage();

        if (
            $memoryUsage < $this->limitOfMemory
            && $cpuUsage < $this->limitOfCpu
        ) {
            return true;
        }

        $this->logInfo('Limit Of memory and Cpu exceeded waiting for task to finish.
        Current memory usage:' . $memoryUsage . ' Cpu usage:' . $cpuUsage);

        return false;
    }

    /**
     * @return string
     */
    public function getCommand()
    {
        return $this->getOption(static::OPTION_TASK_COMMAND);
    }

    /**
     * @return float|int
     */
    public function getMemoryUsage()
    {
        $free = shell_exec('free');
        $free = (string)trim($free);
        $freeArray = explode("\n", $free);
        $memory = explode(" ", $freeArray[1]);
        $memory = array_filter($memory);
        $memory = array_merge($memory);
        $memoryUsage = $memory[1] > 0
            ? $memory[2] / $memory[1] * 100
            : 0;

        return $memoryUsage;
    }

    /**
     * @return mixed
     */
    public function getCpuUsage()
    {
        $load = sys_getloadavg();

        return $load[0];
    }

    /**
     * @return int
     */
    public function getLimitOfCpu()
    {
        return $this->limitOfCpu;
    }

    /**
     * @param int $limitOfCpu
     */
    public function setLimitOfCpu($limitOfCpu)
    {
        $this->limitOfCpu = $limitOfCpu;
    }

    /**
     * @return int
     */
    public function getLimitOfMemory()
    {
        return $this->limitOfMemory;
    }

    /**
     * @param int $limitOfMemory
     */
    public function setLimitOfMemory($limitOfMemory)
    {
        $this->limitOfMemory = $limitOfMemory;
    }

    /**
     * @return Process[]
     */
    public function getProcesses()
    {
        return $this->processes;
    }
}