<?php

namespace NPS\Model;

use NPS\DependencyInjection\IExpertSystemBridge;
use NPS\Repository\ConsultationRepository;
use NPS\Entity\ConsultationEntity;
use NPS\Model\User;
use NPS\Model\KnowledgeBase;
use \DateTime;
use NPS\DependencyInjection\EventDispatcher;
use NPS\DependencyInjection\IConsultationAnswerProvider;
use NPS\Exception\ExpertSystemCommunicationException;
use NPS\Entity\ConsultationAnswerEntity;
use NPS\DependencyInjection\IXMLContentParser;
use NPS\Model\TraversableXMLElement as XML;


/**
 * Consultation process handling
 * @author Lukas Korinek
 */
final class Consultation implements IDomainObject
{
    /** @var ConsultationEntity $entity */
    private $entity;

    /** @var IExpertSystemBridge $es */
    private $es;

    /** @var IXMLContentParser $parser */
    private $parser;

    /** @var KnowledgeBase $base Knowledge base of the consultation */
    private $base;

    /** @var User $user User conducting the consultation */
    private $user;

    public function __construct(
        ConsultationEntity $entity, 
        KnowledgeBase $base, 
        IExpertSystemBridge $es, 
        IXMLContentParser $parser,
        User $user = null)
    {
        $this->entity = $entity;
        $this->es = $es;
        $this->base = $base;
        $this->user = $user;
        $this->parser = $parser;
    }

    ////////////////////////////////////////////////////////////////////
    // Getters

    /**
     * @inheritdoc
     */
    public function getId() 
    {
        return $this->entity->getId();
    }

    /**
     * @inheritdoc
     */
    public function getEntity()
    {
        return $this->entity;
    }

    /**
     * Gets the start date of the consultation
     * @return DateTime
     */
    public function getDateStarted()
    {
        return $this->entity->dateStarted;
    }

    /**
     * Gets the end date of the consultation
     * @return DateTime
     */
    public function getDateEnded()
    {
        return $this->entity->dateEnded;
    }

    /**
     * Gets the user conducting the consultation
     * @return User
     */
    public function getUser()
    {
        return $this->user;
    }

    /**
     * Gets the knowledge base of the consultation
     * @return KnowledgeBase
     */
    public function getKnowledgeBase()
    { 
        return $this->base;
    }

    /**
     * Gets the stored consultation state as array
     * @return array
     */
    private function getState()
    {
        return unserialize($this->entity->state);
    }

    /**
     * Sets the consultation state
     * @param array $state
     */
    private function setState(array $state)
    {
        $this->entity->state = serialize($state);
    }

    /**
     * Is the consultation flagged?
     * @return bool
     */
    public function isFlagged()
    {
        return $this->entity->flagged;
    }

    /**
     * Gets actual question of the consultation
     * @return array
     */
    public function getQuestion()
    {
        if(!$this->es->loadConsultationState($this->getState()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        /** @var XML $question */
        if(!$question = $this->es->getQuestion())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        return $this->parseQuestion($question);
    }

    /**
     * Gets hypothesis of the consultation
     * @return array
     */
    public function getHypothesis()
    {
        if(!$this->es->loadConsultationState($this->getState()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!($hypotheses = $this->es->getHypothesis()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }
        
        return array_map(function($thesis)
        {
            
            $probabilityElement = $thesis->getChild('hypotesis_sumary') ?: $thesis;
            $this->parser->parse($thesis, $this->getKnowledgeBase()->getBaseDiscriminatorTag());

            return [
                'title' => $this->parser->getTitle(),
                'description' => $this->parser->getDescription(),
                'images' => $this->parser->getImages(),
                'sounds' => $this->parser->getSounds(),
                'videos' => $this->parser->getVideos(),
                'probability' => floatval($probabilityElement->getAttribute('probability')),
                'uncertainity' => floatval($probabilityElement->getAttribute('uncertainty')),
                'chance' => floatval($probabilityElement->getAttribute('chance')),
            ];

        }, $hypotheses);
    }

    /**
     * Gets answered questions and the answers
     * @return array
     */
    public function getAnswers()
    {
        return $this->entity->answers->map(function($answer){

            $question = unserialize($answer->question);

            return [
                'question' => $question,
                'answer_id' => $answer->answerId
            ];

        })->toArray();
    }

    //////////////////////////////////////////////////////////

    /**
     * Toggles a flag on this consultation
     */
    public function toggleFlag()
    {
        $this->entity->flagged = !$this->entity->flagged;
    }

    /**
     * Sets an answer to the consultation
     * @param mixed $answerId
     * @return void
     */
    public function setAnswer($answerId)
    { 
        if(!$this->es->loadConsultationState($this->getState()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$question = $this->es->getQuestion())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$this->es->setAnswer($answerId))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(($ended = $this->es->isEnded()) === null)
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$state = $this->es->saveConsultationState())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        $this->setState($state);
        if($ended === true)
        {
            $this->end();
        }

        // Parse the question
        $question = $this->parseQuestion($question);

        // Count it as answered
        $this->entity->questionsAnswered++;
        
        // Remove previously existing answers
        $found = $this->entity->answers->filter(function($ans) use ($question){ return $question['number'] == $ans->questionNumber; });
        if($answer = $found->first())
        {
            $answer->consultation = null;
            $this->entity->answers->removeElement($answer);
        }
        
        // Add new answer entity
        $answer = new ConsultationAnswerEntity();
        $answer->question = serialize($question); 
        $answer->answerId = $answerId;
        $answer->questionNumber = $question['number'];
        $answer->consultation = $this->entity;

        $this->entity->answers->add($answer);
    }

    /**
     * Rewinds the consultation to the previous question
     * @return void
     */
    public function back()
    {
        if(!$this->es->loadConsultationState($this->getState()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$this->es->back())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$state = $this->es->saveConsultationState())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        $this->entity->questionsAnswered--;
        $this->setState($state);
    }

    /**
     * Moves the consultation forward by one question
     * @return bool 
     */
    public function forward()
    {
        if(!$this->es->loadConsultationState($this->getState()))
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$this->es->forward())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        if(!$state = $this->es->saveConsultationState())
        {
            throw new ExpertSystemCommunicationException($this->es->popMessage());
        }

        $this->entity->questionsAnswered++;
        $this->setState($state);
    }

    /**
     * Has the consultation ended already ?
     * @return bool
     */
    public function isEnded()
    {
        return (bool)$this->getDateEnded();
    }

    /**
     * Ends this consultation
     */
    public function end()
    {
        if($this->isEnded())
        {
            throw new \BadMethodCallException('Cannot set consultation as ended once it has already ended');
        }

        $this->entity->dateEnded = new DateTime();
    }

    ///////////////////////////////////////////////////////////////////

    /**
     * Parses a question from XML element to an array 
     * @param XML $question 
     * @return array
     */
    private function parseQuestion(XML $question)
    {
        $answers = [];
        foreach($question->getChild('answers')->getChildren() as $answer)
        {
            $content = $answer->getChild('content') ? $answer->getChild('content')->getText() : $answer->getText(); 
            $answers[$answer->getAttribute('answer_id')] = ucfirst($this->parser->parseText($content));
        }

        $this->parser->parse($question, $this->getKnowledgeBase()->getBaseDiscriminatorTag());
        return [
            'title' => $this->parser->getTitle(),
            'description' => $this->parser->getDescription(),
            'images' => $this->parser->getImages(),
            'sounds' => $this->parser->getSounds(),
            'videos' => $this->parser->getVideos(),
            'answers' => $answers,
            'number' => $this->entity->questionsAnswered + 1,
        ];
    }

}

