class MRNN {
    protected $weight = 0.01;
    protected $lastError = 1;
    protected $smoothing = 0.0001;
    protected $actualResult = 0.01;
    protected $correction = 0;
    protected $epoch = 0;
    protected $trainStats = array();
    protected $statEvery = 5000;
    protected $debug = false;
    protected $activationFunction = 'def';

    public function __construct($activationFunction = 'def') {
        $this->setActivationFunc($activationFunction);
    }

    public function setWeight($weight) {
        $this->weight = $weight;
    }

    protected function setActivationFunc($type) {
        $supportedTypes = array(
            'def' => 'def',
            'sigmoid' => 'sigmoid'
        );

        if (isset($supportedTypes[$type])) {
            $this->activationFunction = $type;
        } else {
            throw new Exception('EX_WRONG_ACTFUNCTION');
        }
    }

    public function processInputData($input) {
        $result = $input * $this->weight;
        return($result);
    }

    public function restoreInputData($output) {
        $result = $output / $this->weight;
        return($result);
    }

    protected function train($input, $expectedResult) {
        switch ($this->activationFunction) {
            case 'def':
                $this->actualResult = $input * $this->weight;
                $this->lastError = $expectedResult - $this->actualResult;
                $this->correction = ($this->lastError / $this->actualResult) * $this->smoothing;
                $this->weight += $this->correction;
                break;
            case 'sigmoid':
                $this->actualResult = $input * $this->weight;
                $this->actualResult = $this->sigmoid($this->actualResult);
                $this->lastError = $expectedResult - $this->unsigmoid($this->actualResult);
                $this->correction = ($this->lastError / $this->actualResult) * $this->smoothing;
                $this->weight += $this->correction;
                break;
        }
    }

    protected function learn($input, $expectedResult) {
        $this->epoch = 0;
        while ($this->lastError > $this->smoothing OR $this->lastError < '-' . $this->smoothing) {
            $this->train($input, $expectedResult);

            if (($this->epoch % $this->statEvery) == 0) {
                $this->trainStats[$this->epoch] = $this->lastError;
            }
            $this->epoch++;
        }
        return(true);
    }

    public function learnDataSet($dataSet, $accel = false) {
        $result = false;
        if (is_array($dataSet)) {
            if (!empty($dataSet)) {
                $totalweight = 0;
                $neurons = array();
                $neuronIndex = 0;
                $prevWeight = $this->weight;
                $networkName = get_class($this);
                foreach ($dataSet as $input => $expectedResult) {
                    $neurons[$neuronIndex] = new $networkName($this->activationFunction);

                    if ($accel) {
                        $neurons[$neuronIndex]->setWeight($prevWeight);
                    }
                    if ($neurons[$neuronIndex]->learn($input, $expectedResult)) {
                        if ($this->debug) {
                            show_success('Trained weight: ' . $neurons[$neuronIndex]->getWeight() . ' on epoch ' . $neurons[$neuronIndex]->getEpoch());
                        }
                        $totalweight += $neurons[$neuronIndex]->getWeight();
                        $this->trainStats[] = $neurons[$neuronIndex]->getTrainStats();
                        $prevWeight = $neurons[$neuronIndex]->getWeight();
                        unset($neurons[$neuronIndex]);
                    }
                    $neuronIndex++;
                }
                $this->weight = $totalweight / $neuronIndex; 
            }
        }
        $result = true;
        return($result);
    }

    public function getWeight() {
        return($this->weight);
    }

    protected function getLastError() {
        return($this->lastError);
    }

    protected function getEpoch() {
        return($this->epoch);
    }
}