<?php
namespace CorpusParole\Models;

use Config;
use CorpusParole\Libraries\Utils;
use CorpusParole\Libraries\CocoonUtils;
use CorpusParole\Libraries\RdfModel\RdfModelResource;
use JsonSerializable;
use Log;
use EasyRdf\Literal;
use EasyRdf\Resource;
use EasyRdf\Graph;
use EasyRdf\Isomorphic;


/**
 * Model class for Document. Inherit from EasyRd\Resource
 * SELECT DISTINCT ?g WHERE {GRAPH ?g {?s ?p ?o}}
 */
class Document extends DocumentBase {

    public function __construct($uri, $graph = null) {
        //                print($graph->dump('html'));
        parent::__construct($uri, $graph);
    }

    private $publishers = null;
    private $mediaArray = null;
    private $transcript = false;
    private $contributors = null;
    private $subjects = null;
    private $types = null;
    private $descriptions = null;
    private $geoInfo = false;
    private $alternativeTitle = false;
    private $rights = false;

    protected function clearMemoizationCache() {
        parent::clearMemoizationCache();
        $this->publishers = null;
        $this->mediaArray = null;
        $this->contributors = null;
        $this->subjects = null;
        $this->types = null;
        $this->descriptions = null;
        $this->transcript = false;
        $this->geoInfo = false;
        $this->rights = false;
    }

    protected function additionalDeltaLists() {

        $geoInfo = $this->getGeoInfo();
        $geoInfoDeltas = is_null($geoInfo)?[]:$geoInfo->getDeltaList();
        return empty($geoInfoDeltas)?[]:[$geoInfoDeltas,];
    }


    private function parseWebResources() {

        $this->mediaArray = [];
        $this->transcript = null;

        $master = $this->get('<http://www.europeana.eu/schemas/edm/isShownBy>');
        $masterUrl = is_null($master)?null:$master->getUri();

        foreach($this->graph->allOfType("<http://www.europeana.eu/schemas/edm/WebResource>") as $webResource) {
            $formatLit = $webResource->getLiteral("dc11:format");
            $format = is_null($formatLit)?null:$formatLit->getValue();
            if(is_null($format)) {
                throw new ModelsException("parseWebResources: No dc:11 format on web resource");
            }

            if(0 === strpos($format, 'audio/') ||
               0 === strpos($format, 'video/') ||
               0 === strpos($format, 'Sampling:') ) {
                array_push(
                    $this->mediaArray,
                    new MediaResource(
                        $webResource->getUri(),
                        $this->graph,
                        (($webResource->getUri() === $masterUrl)?true:false))
                );
            } else if(
                0 === strpos($format, 'application/xml') ||
                0 === strpos($format, 'application/pdf') ) {
                $this->transcript = new TranscriptResource($webResource->getUri(), $this->graph);
            }
            else {
                throw new ModelsException("parseWebResources: unknown format");
            }
        }

    }

    public function getMediaArray() {

        if(is_null($this->mediaArray)) {
            $this->parseWebResources();
        }
        return $this->mediaArray;
    }

    public function getTypes() {
        if(is_null($this->types)) {
            $this->types = $this->getProvidedCHO()->all('dc11:type');
        }
        return $this->types;
    }

    public function getDiscourseTypes() {
        return array_values(array_filter($this->getTypes(), function($v) {
            return $v instanceof Resource
                && preg_match(config('corpusparole.bnf_ark_url_regexp'), $v->getUri())
                && array_key_exists($v->getUri(), config('corpusparole.corpus_discourse_type'));
        }));
    }

    public function getOtherTypes() {
        $res = array_values(array_filter($this->getTypes(), function($v) {
            return !($v instanceof Resource)
                || !preg_match(config('corpusparole.bnf_ark_url_regexp'), $v->getUri())
                || !array_key_exists($v->getUri(), config('corpusparole.corpus_discourse_type'));
        }));
        return $res;
    }

    public function getTranscript() {
        if($this->transcript === false) {
            $this->parseWebResources();
        }
        return $this->transcript;
    }

    public function getContributors() {
        if(is_null($this->contributors)) {
            $roleList = CocoonUtils::OLAC_ROLES;
            array_push($roleList, 'http://purl.org/dc/elements/1.1/creator');
            $this->contributors = array_reduce(
                $roleList,
                function($res, $olacRole) {
                    return array_merge(
                        $res,
                        array_map(
                            function($olacValue) use ($olacRole) {
                                return [
                                    'name' => ($olacValue instanceof Literal)?$olacValue->getValue():null,
                                    'nameLiteral' => ($olacValue instanceof Literal)?$olacValue:null,
                                    'url' => ($olacValue instanceof Resource)?$olacValue->getUri():null,
                                    'role' => $olacRole
                                ];
                            },
                            $this->getProvidedCHO()->all("<$olacRole>")
                        )
                    );
                },
                []
            );
        }
        return $this->contributors;
    }

    /**
     * change contributors list
     */
    public function setContributors($contributors) {
        $delta = $this->startDelta();
        //remove old,
        foreach ($this->getContributors() as $contribDef) {
            $value = null;
            if (empty($contribDef['url'])) {
                if(empty($contribDef['nameLiteral'])) {
                    $value = new Literal($contribDef['name']);
                } else {
                    $value = $contribDef['nameLiteral'];
                }
            } else {
                $value = new Resource($contribDef['url']);
            }
            $this->getProvidedCHO()->delete($contribDef['role'], $value);
            $delta->getDeletedGraph()->add($this->getProvidedCHO(), $contribDef['role'], $value);
        }

        //put new
        foreach ($contributors as $newContribDef) {
            $value = null;
            if (empty($newContribDef['url'])) {
                $value = new Literal($newContribDef['name'], "fr", null);
            } else {
                $value = new Resource($newContribDef['url']);
            }
            $this->getProvidedCHO()->add($newContribDef['role'], $value);
            $delta->getAddedGraph()->add($this->getProvidedCHO(), $newContribDef['role'], $value);
        }

        $this->contributors = null;

    }

    /**
     * change discourse type list
     */
    public function updateDiscourseTypes(array $discoursesTypes) {

        $this->startDelta();

        //delete
        foreach($this->getDiscourseTypes() as $discourseType) {
            $this->getProvidedCHO()->delete('dc11:type', $discourseType);
            $this->currentDelta->getDeletedGraph()->addResource($this->getProvidedCHO(), 'dc11:type', $discourseType->getUri());
        }

        // and re-add them
        foreach($discoursesTypes as $dType) {
            $this->getProvidedCHO()->addResource('dc11:type', $dType);
            $this->currentDelta->getAddedGraph()->addResource($this->getProvidedCHO(), 'dc11:type', $dType);
        }

        $this->clearMemoizationCache();
    }

    /**
     * Get subjects list
     */
    public function getSubjects() {
        if(is_null($this->subjects)) {
            $this->subjects = $this->getProvidedCHO()->all('<http://purl.org/dc/elements/1.1/subject>');
        }
        return $this->subjects;
    }

    /**
     * get the GeoInfoObject
     * Ths returned object should be limited for read only activities.
     * If it needs to be edited use the addGeoInfo method.
     */
    public function getGeoInfo() {
        if($this->geoInfo === false) {
            $places = $this->getProvidedCHO()->all('<http://purl.org/dc/terms/spatial>');
            $this->geoInfo = null;
            if($places) {
                $this->geoInfo = new GeoResource($places[0]->getUri(), $this->graph, $this->getProvidedCHO());
            }
        }
        return $this->geoInfo;
    }

    public function addGeoInfo() {
        $geoInfo = $this->getGeoInfo();
        if(!is_null($geoInfo)) {
            // if there already is a geo info, just return it.
            $geoInfo->setReadOnly(false);
            $geoInfo->setNeedDelete(true);
            return $geoInfo;
         }

        $this->geoInfo = false;

        $geoinfoNode = $this->getGraph()->newBNode("http://www.europeana.eu/schemas/edm/Place");
        $this->getProvidedCHO()->addResource("http://purl.org/dc/terms/spatial", $geoinfoNode);

        $this->geoInfo = new GeoResource($geoinfoNode->getUri(), $this->graph, $this->getProvidedCHO());
        $this->geoInfo->setReadOnly(false);
        $this->geoInfo->setNeedDelete(false);

        return $this->geoInfo;
    }

    /**
     * change subjecs list
     */
    public function setSubjects($subjects) {
        $delta = $this->startDelta();
        //remove old,
        foreach ($this->getSubjects() as $subject) {
            $this->getProvidedCHO()->delete('<http://purl.org/dc/elements/1.1/subject>', $subject);
            $delta->getDeletedGraph()->add($this->getProvidedCHO(), 'http://purl.org/dc/elements/1.1/subject', $subject);
        }

        //put new
        foreach ($subjects as $newSubject) {
            $value = null;
            if(is_string($newSubject) && filter_var($newSubject, FILTER_VALIDATE_URL)) {
                $value = new Resource($newSubject);
            }
            elseif (is_string($newSubject)) {
                $value = new Literal($newSubject, null, null);
            } elseif(is_array($newSubject)) {
                $value = new Literal(isset($newSubject['value'])?$newSubject['value']:null, isset($newSubject['lang'])?$newSubject['lang']:null, isset($newSubject['datatype'])?$newSubject['value']:null);
            }

            $this->getProvidedCHO()->add('http://purl.org/dc/elements/1.1/subject', $value);
            $delta->getAddedGraph()->add($this->getProvidedCHO(), 'http://purl.org/dc/elements/1.1/subject', $value);
        }

        $this->subjects = null;

    }

    public function getPublishers() {
        if(is_null($this->publishers)) {
            try {
                $this->publishers = $this->getProvidedCHO()->all('dc11:publisher');
            } catch(\Exception $e) {
               $this->publishers = [];
            }
        }
        return $this->publishers;
    }

    public function getPublishersValues() {
        $publishers = $this->getPublishers();
        return array_map(
            function($v) { return Utils::processLiteralResourceOrString($v); },
            $this->getPublishers()
        );
    }


    /**
     * Get descriptions
     */
    public function getDescriptions() {
        if(is_null($this->descriptions)) {
            $this->descriptions = [];
            $this->descriptions = array_merge($this->descriptions, $this->getProvidedCHO()->all('<http://purl.org/dc/elements/1.1/description>'));
            $this->descriptions = array_merge($this->descriptions, $this->getProvidedCHO()->all('<http://purl.org/dc/terms/abstract>'));
            $this->descriptions = array_merge($this->descriptions, $this->getProvidedCHO()->all('<http://purl.org/dc/terms/tableOfContents>'));
        }
        return $this->descriptions;
    }

    /**
     * Get Alternative Title
     */
    public function getAlternativeTitle() {
        if($this->alternativeTitle === false) {
            try {
                $this->alternativeTitle = $this->getProvidedCHO()->getLiteral('<http://purl.org/dc/terms/alternative>');
            } catch(\Exception $e) {
                $this->alternativeTitle = null;
            }
        }
        return $this->alternativeTitle;
    }

    public function getAlternativeTitleValue() {
        $alternativeTitle = $this->getAlternativeTitle();
        return is_null($alternativeTitle)?null:$alternativeTitle->getValue();
    }


    /**
     * Get Rights
     */
    public function getRights() {
        if($this->rights === false) {
            try {
                $this->rights = $this->getResource('<http://www.europeana.eu/schemas/edm/rights>');
            } catch(\Exception $e) {
                $this->rights = null;
            }
        }
        return $this->rights;
    }

    public function getRightsValue() {
        $rights = $this->getRights();
        return is_null($rights)?null:$rights->getUri();
    }

    public function isIsomorphic($doc) {
        return Isomorphic::isomorphic($this->graph, $doc->graph);
    }

    /*
     * Clone document.
     * clone also the innerDocumenent
     */
    public function __clone() {
        if(!is_null($this->graph)) {
            $this->graph = new Graph($this->graph->getUri(), $this->graph->toRdfPhp());
        }
    }

    public function jsonSerialize() {

        $res = parent::jsonSerialize();
        if($this->graph) {

            $mediaArray =  is_null($this->getMediaArray())?
                        null:
                        array_combine(
                            array_map(function($m) { return $m->getUrl(); }, $this->getMediaArray()),
                            array_map(
                                function($m) {
                                    return $m->jsonSerialize();},
                                $this->getMediaArray()
                            )
                        );

            $transcript = is_null($this->getTranscript())?null:$this->getTranscript()->jsonSerialize();
            $geoInfo = is_null($this->getGeoInfo())?null:$this->getGeoInfo()->jsonSerialize();

            $publishers = $this->getPublishersValues();

            $contributors = array_map(
                function($c) { unset($c['nameLiteral']); return $c; },
                $this->getContributors()
            );

            $subjects = array_map(
                function($s) { return Utils::processLiteralResourceOrString($s); },
                $this->getSubjects()
            );
            $types = array_map(
                function($s) { return Utils::processLiteralResourceOrString($s); },
                $this->getTypes()
            );
            $descriptions = array_map(
                function($s) { return Utils::processLiteralResourceOrString($s); },
                $this->getDescriptions()
            );

            $res = array_merge($res, [
                'publishers' => $publishers,
                'contributors' => $contributors,
                'subjects' => $subjects,
                'types' => $types,
                'transcript' => $transcript,
                'mediaArray'=> $mediaArray,
                'geoInfo' => $geoInfo,
                'descriptions' => $descriptions,
                'alternativeTitle' => $this->getAlternativeTitleValue(),
                'rights' => $this->getRightsValue()
            ]);

        }
        return $res;

    }

}
