<?php

namespace Velis\Filter;

use Laminas\Filter\FilterInterface;
use Velis\Output;

/**
 * Number to text filter
 */
class NumberToText implements FilterInterface
{
    /**
     * Default language code
     * @var String
     */
    protected $_langId = 'pl';


    /**
     * Active words set
     * @var array
     */
    protected $_words = [];


    /**
     * Number words and their declinations
     * @var array
     */
    protected $_dictionary = [
        'pl' => [
            'minus',
            [
                'zero',
                'jeden',
                'dwa',
                'trzy',
                'cztery',
                'pięć',
                'sześć',
                'siedem',
                'osiem',
                'dziewięć'],
            [
                'dziesięć',
                'jedenaście',
                'dwanaście',
                'trzynaście',
                'czternaście',
                'piętnaście',
                'szesnaście',
                'siedemnaście',
                'osiemnaście',
                'dziewiętnaście'],
            [
                'dziesięć',
                'dwadzieścia',
                'trzydzieści',
                'czterdzieści',
                'pięćdziesiąt',
                'sześćdziesiąt',
                'siedemdziesiąt',
                'osiemdziesiąt',
                'dziewięćdziesiąt'],
            [
                'sto',
                'dwieście',
                'trzysta',
                'czterysta',
                'pięćset',
                'sześćset',
                'siedemset',
                'osiemset',
                'dziewięćset'],
            [
                'tysiąc',
                'tysiące',
                'tysięcy'],
            [
                'milion',
                'miliony',
                'milionów'],
            [
                'miliard',
                'miliardy',
                'miliardów'],
            [
                'bilion',
                'biliony',
                'bilionów']
        ],
        'en' => [
            'minus',
            [
                'zero',
                'one',
                'two',
                'three',
                'four',
                'five',
                'six',
                'seven',
                'eight',
                'nine'],
            [
                'ten',
                'eleven',
                'twelve',
                'thirteen',
                'fourteen',
                'fifteen',
                'sixteen',
                'seventeen',
                'eighteen',
                'nineteen'],
            [
                'ten',
                'twenty',
                'thirty',
                'forty',
                'fifty',
                'sixty',
                'seventy',
                'eighty',
                'ninety'],
            [
                'one hundred',
                'two hundred',
                'three hundred',
                'four hundred',
                'five hundred',
                'six hundred',
                'seven hundred',
                'eight hundred',
                'nine hundred'],
            [
                'thousand',
                'thousand',
                'thousand'],
            [
                'million',
                'million',
                'million'],
            [
                'billion',
                'billion',
                'billion'],
            [
                'trillion',
                'trillion',
                'trillion']
        ]
    ];


    /**
     * Declination constructor
     * @param String $langId
     */
    public function __construct($langId = null)
    {
        if ($langId && $this->_dictionary[$langId]) {
            $this->_langId = $langId;
        }

        $this->_words = $this->_dictionary[$this->_langId];
    }


    /**
     * Returns active language code
     * @return String
     */
    public function getLangId()
    {
        return $this->_langId;
    }


    /**
     * Set new language
     * @param String $langId
     */
    public function setLang($langId)
    {
        if ($this->_dictionary[$langId]) {
            $this->_langId = $langId;
            $this->_words = $this->_dictionary[$langId];
        } else {
            $this->setDefaultLang();
        }
    }


    /**
     * Returns active words set
     * @return array
     */
    public function getWords()
    {
        return $this->_words;
    }


    /**
     * Set default language
     */
    private function setDefaultLang()
    {
        $this->_langId = 'pl';
        $this->_words = $this->_dictionary['pl'];
    }


    /**
     * Number tail declination
     * @param array $varieties, integer $value
     * @return String
     */
    protected function declination($varieties, $value)
    {
        $output = $varieties[2];

        $units = (int) substr($value, -1);
        $parts = $value % 100;

        if ($value == 1) {
            $output = $varieties[0];
        } elseif (($units > 1 && $units < 5) & !($parts > 10 && $parts < 20)) {
            $output = $varieties[1];
        }

        return $output;
    }


    /**
     * Text representation of number up to 1000
     * @param integer $value
     * @return String
     */
    protected function convertNumber($value)
    {
        $output = '';
        $absolute = abs((int) $value);

        if ($absolute == 0) {
            return $this->_words[1][0];
        }
        $parts = $absolute % 10;
        $ten = ($absolute % 100 - $parts) / 10;
        $hundreds = ($absolute - $ten * 10 - $parts) / 100;

        if ($hundreds > 0) {
            $output .= $this->_words[4][$hundreds - 1] . ' ';
        }

        if ($ten > 0) {
            if ($ten == 1) {
                $output .= $this->_words[2][$parts] . ' ';
            } else {
                $output .= $this->_words[3][$ten - 1] . ' ';
            }
        }

        if ($parts > 0 && $ten != 1) {
            $output .= $this->_words[1][$parts] . ' ';
        }
        return $output;
    }


    /**
     * Converts number to text with declination
     * @param integer $value
     * @return String
     */
    protected function convertToText($value)
    {
        $number = preg_replace('/[^-\d]+/', '', $value);
        $output = '';

        // if number is negative add 'minus'
        if ($number[0] == '-') {
            $number = substr($number, 1);
            $output = $this->_words[0] . ' ';
        }

        $txt = str_split(strrev($number), 3);

        if ($number == 0) {
            $output = $this->_words[1][0] . ' ';
        }

        for ($i = count($txt) - 1; $i >= 0; $i--) {
            $number = (int) strrev($txt[$i]);

            if ($i == 0) {
                $output .= $this->convertNumber($number) . ' ';
            } else {
                $output .= ( $number > 1 ? $this->convertNumber($number) . ' ' : '')
                        . $this->declination($this->_words[4 + $i], $number) . ' ';
            }
        }
        return trim($output);
    }


    /**
     * Returns number representation as word
     * @param string $value, string $currency, string $langId
     * @return string
     */
    public function filter($value, $currency = null, $langId = null)
    {
        if (!$currency) {
            $currency = 'PLN';
        }

        if ($langId) {
            $this->setLang($langId);
        }

        $value = Output::formatFloat($value);
        $locale = localeconv();
        $amount = explode($locale['decimal_point'] ?: '.', $value);

        $units = preg_replace('/[^-\d]+/', '', $amount[0]);
        $parts = preg_replace('/[^\d]+/', '', substr(isset($amount[1]) ? $amount[1] : 0, 0, 2));
        while (strlen($parts) < 2) {
            $parts .= '0';
        }

        return $this->convertToText($units) . ' ' . $parts . '/100' . ' ' . $currency;
    }


    /**
     * Invoke magic method
     *
     * @param string $text
     * @return string
     */
    public function __invoke($number, $currency = null, $langId = null)
    {
        return $this->filter($number, $currency, $langId);
    }
}
