<?php
namespace CorpusParole\Services;

use Cache;
use CorpusParole\Services\BnfResolverInterface;
use CorpusParole\Services\BnfResolverTimeoutException;
use EasyRdf;

class BnfResolver implements BnfResolverInterface {

    public function __construct($sparqlClient) {
        $this->sparqlClient = $sparqlClient;
    }

    private function checkBnfId($id) {
        $bnfid = $id;

        if(strpos($id, config('corpusparole.bnf_ark_base_url')) === 0) {
            $bnfid = config('corpusparole.bnf_base_url').substr($id, strlen(config('corpusparole.bnf_ark_base_url')));
        }
        elseif(strpos($id, config('corpusparole.bnf_base_url')) !== 0) {
            $bnfid = config('corpusparole.bnf_base_url').$id;
        }
        $bnfid = rtrim($bnfid, '/');
        if(preg_match("/^".preg_quote(config('corpusparole.bnf_base_url'),"/")."ark\:\/12148\/[[:alnum:]]/", $bnfid) !== 1) {
            throw new BnfResolverException("the provided id \"$id\" is not a BNF id");
        }
        return $bnfid;
    }

    /**
     * Get label from BNF id
     * @param string $id The id to resolve. Can be an url starting with http://data.bnf.fr/ or http://ark.bnf.fr/
     * @return a string with the name
     */
    public function getLabel($id) {
        $res = $this->getlabels([$id,]);
        assert(array_key_exists($id,$res), "the result must contains $id");
        return $res[$id];
    }

    /**
     * Get a list of names from an array of viaf ids.
     * @param array $ids The array of ids to resolve.
     *                   Each id can be an url starting with http://data.bnf.fr/ or http://ark.bnf.fr/
     */
    public function getLabels(array $ids) {

        if(count($ids) > config('corpusparole.bnf_max_ids')) {

            return array_reduce(
                array_map([$this, 'getLabels'], array_chunk($ids, config('corpusparole.bnf_max_ids'))),
                'array_merge',
                []
            );
            //throw new BnfResolverException("Too manys ids provided");
        }

        $bnfids = array_map([$this, 'checkBnfId'], $ids);
        $bnfidsMap = array_combine($bnfids, $ids);

        $results = [];
        $missingBnfids = [];

        foreach ($bnfidsMap as $bnfid => $bnfidSource) {
            $cachedValue = Cache::get("bnf:$bnfid");
            if(is_null($cachedValue)) {
                array_push($missingBnfids, $bnfid);
            } elseif (mb_strlen($cachedValue)>0) {
                $results[$bnfidSource] = $cachedValue;
            } else {
                $results[$bnfidSource] = null;
            }
        }

        if(count($missingBnfids) == 0) {
            return $results;
        }

        $query = "SELECT ?s ?o WHERE {";
        foreach ($missingBnfids as $index => $bid) {
            if($index > 0) {
                $query .= " UNION ";
            }
            $query .= "{ <$bid> <http://www.w3.org/2004/02/skos/core#prefLabel> ?o. ?s <http://www.w3.org/2004/02/skos/core#prefLabel> ?o. FILTER(?s = <$bid> && lang(?o) = \"fr\")}";
        }
        $query .= "}";

        try {
            $docs = $this->sparqlClient->query($query);
        } catch (EasyRdf\Exception $e) {
            $code = 0;
            if(method_exists($e, 'getCode')) {
                $code = $e->getCode();
            }
            $message = $e->getMessage();
            if($code == 400 || ($code == 0 && stripos($message, 'timed out')>=0) ) {
                throw new BnfResolverTimeoutException("Query to bnf server timed out.");
            }
            // reraise the original exception
            throw $e;
        }

        $resultsRaw = [];

        foreach ($docs as $doc) {
            $bnfid = $doc->s->getUri();
            $bnflabel = $doc->o;

            $value = $bnflabel->getValue();

            if(!empty($value)) {
                $resultsRaw[$bnfid] = $bnflabel;
            }
        }

        foreach ($missingBnfids as $bnfid) {
            $bnfidSource = $bnfidsMap[$bnfid];
            $missingValue = (array_key_exists($bnfid,$resultsRaw) && $resultsRaw[$bnfid])?mb_strtolower($resultsRaw[$bnfid]->getValue()):"";
            if (mb_strlen($missingValue)>0) {
                Cache::put("bnf:$bnfid", $missingValue, config('corpusparole.bnf_cache_expiration'));
                $results[$bnfidSource] = $missingValue;
            }
            else {
                Cache::put("bnf:$bnfid", "", config('corpusparole.bnf_cache_expiration'));
                $results[$bnfidSource] = null;
            }
        }
        return $results;
    }

}
