<?php
namespace CorpusParole\Services;

use CorpusParole\Services\GeonamesResolverInterface;

use Cache;
use Config;
use EasyRdf\Graph;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Contracts\Cache\Repository as CacheRepository;

class GeonamesResolver implements GeonamesResolverInterface {

    private $client = null;

    public function __construct(Client $httpClient) {
        $this->client = $httpClient;
    }

    /**
     * make the Geonames query.
     */
    public function queryLabel($id) {

        $url = config('corpusparole.geonames_base_url').$id."/";

        try {
            $response = $this->client->get($url, ['headers'=>['Accept' => 'application/rdf+xml'],]);
        }
        catch(ClientException $e) {
            if($e->getResponse()->getStatusCode() === 404) {
                return null;
            } else {
                throw new GeonamesResolverException(
                    $e->getMessage(),
                    $e->getResponse()->getStatusCode(),
                    $e
                );
            }
        }
        catch(RequestException $e) {
            throw new GeonamesResolverException(
                $e->getMessage(),
                $e->hasResponse()?$e->getResponse()->getStatusCode():500,
                $e
            );
        }

        $graph = new Graph($url, $response->getBody());
        $labels = [];
        // search First offficial name in fr then name
        foreach ($graph->allLiterals("<$url>", "<http://www.geonames.org/ontology#officialName>") as $labelLit) {
            $lang = $labelLit->getLang();
            if(!$lang && !isset($labels[''])) {
                $labels[''] = $labelLit->getvalue();
            }
            elseif (strpos($lang, 'fr') === 0 && !isset($labels['fr'])) {
                $labels['fr'] = $labelLit->getvalue();
            }
            elseif (strpos($lang, 'en') === 0 && !isset($labels['en'])) {
                $labels['en'] = $labelLit->getvalue();
            }
        }

        $label = isset($labels['fr']) ? $labels['fr'] : null ;

        if(is_null($label)) {
            foreach ($graph->allLiterals("<$url>", "<http://www.geonames.org/ontology#name>") as $labelLit) {
                $lang = $labelLit->getLang();
                if(!$lang && !isset($labels[''])) {
                    $labels[''] = $labelLit->getvalue();
                }
                elseif (strpos($lang, 'fr') === 0 && !isset($labels['fr'])) {
                    $labels['fr'] = $labelLit->getvalue();
                }
                elseif (strpos($lang, 'en') === 0 && !isset($labels['en'])) {
                    $labels['en'] = $labelLit->getvalue();
                }
            }
            $label = isset($labels['fr']) ? $labels['fr'] : (
                isset($labels['en']) ? $labels['en'] : (
                    isset($labels['']) ? $labels[''] : null
                    )
                ) ;
        }

        return $label;
    }

    /**
     * Check Geonames id format
     */
    private function checkGeonamesIdFormat($geonamesid) {
        return ctype_digit($geonamesid);
    }

    /**
     * Get name from Geonames id
     * @param string $id The id to resolve. Can be an url starting with http://sws.geonames.org/
     * @return a string with the name
     */
    public function getLabel($id) {
        $geonamesid = $id;
        $matches = [];
        if( preg_match(config('corpusparole.geonames_url_regexp'), $id, $matches) === 1) {
            $geonamesid = $matches[1];
        }
        $geonamesid = rtrim($geonamesid, '/');

        if(!$this->checkGeonamesIdFormat($geonamesid)) {
            throw new GeonamesResolverException("GeonamesId not in correct format", 400);
        }

        $that = $this;

        return Cache::remember("geonames:$geonamesid", config('corpusparole.geonames_cache_expiration'), function() use ($that, $geonamesid)  {

            return $that->queryLabel($geonamesid);

        });

    }

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

        if(count($ids) > config('corpusparole.geonames_max_ids')) {
            throw new GeonamesResolverException("Too manys ids provided");
        }

        return array_combine($ids, array_map([$this,'getLabel'], $ids));
    }


}
