<?php 

namespace NPS\DependencyInjection;

use NPS\Model\TraversableXMLElement as XML;
use NPS\Exception\ParserNotCalledException;
use NPS\Etc\Strings;
use NPS\Etc\Files;
use NPS\Repository\FileRepository;

/**
 * Content parser for NPS
 * @author Lukas Korinek
 */
final class NPSParser implements IXMLContentParser
{

    /** @var string|null */
    private $title;

    /** @var string|null */
    private $description;

    /** @var array */
    private $images;

    /** @var array */
    private $videos;

    /** @var array */
    private $sounds;

    /** @var bool */
    private $parsed;

    /** @var string $tag */
    private $tag;

    /** @var FileRepository $fileRepo */
    private $fileRepo;
    
    public function __construct(FileRepository $fileRepo)
    {
        $this->fileRepo = $fileRepo;
        $this->parsed = false;
    }

    /**
     * @inheritdoc
     */
    public function parse(XML $xml, $discriminatorTag)
    {
        $this->parsed = false;
        $this->tag = strtolower(trim($discriminatorTag)) ?: null;
        $this->title = null;
        $this->description = null;
        $this->images = [];
        $this->videos = [];
        $this->sounds = []; 


        $content = $xml->getChild('content');
        if($content && count($content->getChildren()))
        {
            $this->parseEnhanced($xml);
            $this->parsed = true;
            return;
        }
        
        $this->parseBasic($xml);
        $this->parsed = true;
    }

    /**
     * @inheritdoc
     */
    public function parseText($str)
    {
        // Upper and lower index support
        $str = preg_replace('/_\{([^}]+)\}/', '<sub>$1</sub>', $str);
        $str = preg_replace('/\^\{([^}]+)\}/', '<sup>$1</sup>', $str);

        return trim($str);
    }


    /**
     * @inheritdoc
     */
    public function getTitle()
    {
        $this->guard();
        return $this->title;
    }

    /**
     * @inheritdoc
     */
    public function getDescription()
    {
        $this->guard();
        return $this->description;
    }

    /**
     * @inheritdoc
     */
    public function getImages()
    {
        $this->guard();
        return $this->images;
    }

    /**
     * @inheritdoc
     */
    public function getVideos()
    {
        $this->guard();
        return $this->videos;
    }

    /**
     * @inheritdoc
     */
    public function getSounds()
    {
        $this->guard();
        return $this->sounds;
    }

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

    /**
     * Guards the getters against calling prior to any parsing operation
     * @throws ParserNotCalledException
     */
    private function guard()
    {
        if(!$this->parsed)
        {
            throw new ParserNotCalledException();
        }
    }

    /**
     * Parses simple text
     * @param XML $xml
     */
    private function parseBasic(XML $xml)
    {
        $content = $xml->getChild('content');
        $this->title = ucfirst(trim($content ? $content->getText() : $xml->getText()));
    }

    /**
     * Parses new enhanced question/hypothesis/answer format
     * @param XML $xml
     */
    private function parseEnhanced(XML $xml)
    {
        $content = $xml->getChild('content');

        $title = $content->getChild('title');
        $this->title = ucfirst($this->parseText($title ? $title->getText() : $content->getText()));

        $description = $content->getChild('description');
        $this->description = $description ? $this->parseText($description->getText()) : null;

        $images = $content->getChildren('image');
        $this->images = $this->parseMultimedia($images);

        $sounds = $content->getChildren('sound');
        $this->sounds = $this->parseMultimedia($sounds);

        $videos = $content->getChildren('video');
        $this->videos = $this->parseMultimedia($videos);
    }

    /**
     * Validates the elements for a valid uri link and returns array of valid links
     * @param array $elements 
     * @return array
     */
    private function parseMultimedia(array $elements)
    {
        $links = array_map(function($elem){
            return $this->getLink($elem->getText());
        }, $elements);

        return array_values(array_filter($links));
    }

    /**
     * Gets a link by URI
     * @param string $uri
     */
    private function getLink($uri)
    {
        if($this->isRemoteUri($uri))
        {
            return $uri;
        }

        return $this->getLocalPublicLink($uri);
    }

    /**
     * Gets a local public link by uri
     * Also handles proper browser cache clearance
     * @param string $uri 
     * @return string|null
     */
    private function getLocalPublicLink($uri)
    {
        if(is_numeric($uri))
        {
            // Numeric URI serves for identification via file id 
            if($file = $this->fileRepo->getById((int)$uri))
            {
                return $file->getUrl();
            }
        }
        else if(!empty($this->tag))
        {
            // Stringish URIs must have a filename with extension format, e.g. sound.wav
            // A tag and filename combination makes a unique key to look for 
            // If any of them is absent, it has no sense to even look for it
            if($file = $this->fileRepo->getByTagAndFilename($this->tag, trim($uri)))
            {
                return $file->getUrl();
            }
        }
        
        return null;
    }

    /**
     * Decides whether the URI is local or remote
     * @param string $uri 
     * @return bool
     */
    private function isRemoteUri($uri)
    {
        return is_string($uri) &&  (Strings::starts('http://', $uri) || Strings::starts('https://', $uri));
    }
}

