<?php

namespace Fisharebest\Localization;

use DomainException;
use Fisharebest\Localization\Locale\LocaleInterface;

/**
 * Class Locale - Static functions to generate and compare locales.
 *
 * @author    Greg Roach <fisharebest@gmail.com>
 * @copyright (c) 2018 Greg Roach
 * @license   GPLv3+
 */
class Locale
{
    /**
     * Callback for PHP sort functions - allows lists of locales to be sorted.
     * Diacritics are removed and text is capitalized to allow fast/simple sorting.
     *
     * @param LocaleInterface $x
     * @param LocaleInterface $y
     *
     * @return int
     */
    public static function compare(LocaleInterface $x, LocaleInterface $y)
    {
        return strcmp($x->endonymSortable(), $y->endonymSortable());
    }

    /**
     * Create a locale from a language tag (or locale code).
     *
     * @param string $code
     *
     * @return LocaleInterface
     * @throws DomainException
     */
    public static function create($code)
    {
        $class = __NAMESPACE__ . '\Locale\Locale' . implode(array_map(function ($x) {
            return ucfirst(strtolower($x));
        }, preg_split('/[^a-zA-Z0-9]+/', $code)));

        if (class_exists($class)) {
            return new $class();
        } else {
            throw new DomainException($code);
        }
    }

    /**
     * Create a locale from a language tag (or locale code).
     *
     * @param string[]          $server    The $_SERVER array
     * @param LocaleInterface[] $available All locales supported by the application
     * @param LocaleInterface   $default   Locale to show in no matching locales
     *
     * @return LocaleInterface
     */
    public static function httpAcceptLanguage(array $server, array $available, LocaleInterface $default)
    {
        if (!empty($server['HTTP_ACCEPT_LANGUAGE'])) {
            $http_accept_language = strtolower(str_replace(' ', '', $server['HTTP_ACCEPT_LANGUAGE']));
            preg_match_all('/(?:([a-z][a-z0-9_-]+)(?:;q=([0-9.]+))?)/', $http_accept_language, $match);
            $preferences = array_map(function ($x) {
                return $x === '' ? 1.0 : (float) $x;
            }, array_combine($match[1], $match[2]));

            // Need a stable sort, as the original order is significant
            $preferences = array_map(function ($x) {
                static $n = 0;

                return array($x, --$n);
            }, $preferences);
            arsort($preferences);
            $preferences = array_map(function ($x) {
                return $x[0];
            }, $preferences);

            // If "de-DE" requested, but not "de", then add it at a lower priority
            foreach ($preferences as $code => $priority) {
                if (preg_match('/^([a-z]+)[^a-z]/', $code, $match) && !isset($preferences[$match[1]])) {
                    $preferences[$match[1]] = $priority * 0.5;
                }
            }

            foreach (array_keys($preferences) as $code) {
                try {
                    $locale = Locale::create($code);
                    if (in_array($locale, $available)) {
                        return $locale;
                    }
                } catch (DomainException $ex) {
                    // An unknown locale?  Ignore it.
                }
            }
        }

        return $default;
    }
}
