<?php

/**
 * Frequency parser.
 *
 * @author       Time.ly Network, Inc.
 * @since        2.0
 * @instantiator new
 * @package      Ai1EC
 * @subpackage   Ai1EC.Parser
 */
class Ai1ec_Frequency_Utility {

    /**
     * @var array Map of default multipliers
     */
    protected $_multipliers = array(
        's' => 1, // take care, to always have an identifier with unit of `1`
        'm' => 60,
        'h' => 3600,
        'd' => 86400,
        'w' => 604800,
    );

    /**
     * @var array Map of WordPress native multipliers
     */
    protected $_wp_names = array(
        'hourly'     => array(
            'item'    => array( 'h' => 1 ),
            'seconds' => 3600
        ),
        'twicedaily' => array(
            'item'    => array( 'd' => 0.5 ),
            'seconds' => 43200
        ),
        'daily'      => array(
            'item'    => array( 'd' => 1 ),
            'seconds' => 86400
        ),
    );

    /**
     * @var string One letter code for lowest available quantifier
     */
    protected $_lowest_quantifier = 's';

    /**
     * @var array Parsed representation - quantifiers and their amounts
     */
    protected $_parsed = array();

    /**
     * Inject different multiplier
     *
     * Add multiplier, to parseable characters
     *
     * @param string $letter Letter (single ASCII letter) to allow as quantifier
     * @param int    $quant  Number of seconds quantifier represents
     *
     * @return Ai1ec_Frequency_Utility Instance of self for chaining
     *
     * @throws Ai1ec_Invalid_Argument_Exception If first argument is not
     *                                          an ASCII letter.
     */
    public function add_multiplier( $letter, $quant ) {
        $letter   = substr( (string)$letter, 0, 1 );
        $quant = (int)$quant;
        if ( $quant < 0 || ! preg_match( '/^[a-z]$/i', $letter ) ) {
            throw new Ai1ec_Invalid_Argument_Exception(
                'First argument to add_multiplier must be ASCII letter' .
                '(a-zA-Z), and second - an integer'
            );
        }
        $this->_multipliers[$letter] = $quant;
        return $this;
    }

    /**
     * Parse user input
     *
     * Convert arbitrary user input (i.e. "2w 10h") to internal representation
     *
     * @param string $input User input for frequency
     *
     * @return bool Success
     */
    public function parse( $input ) {
        $input = strtolower(
            preg_replace(
                '|(\d*\.?\d+)\s+([a-z])|',
                '$1$2',
                trim( $input )
            )
        );
        if ( isset( $this->_wp_names[$input] ) ) {
            $this->_parsed = $this->_wp_names[$input]['item'];
            return true;
        }
        $match = $this->_match( $input );
        if ( ! $match ) {
            return false;
        }
        $this->_parsed = $match;
        return true;
    }

    /**
     * Convert parsed input to corresponding seconds
     *
     * @return int Number of seconds corresponding to user input
     */
    public function to_seconds() {
        $seconds = 0;
        foreach ( $this->_parsed as $quantifier => $number ) {
            $seconds += $number * $this->_multipliers[$quantifier];
        }
        $seconds = (int)$seconds; // discard any fractional part
        return $seconds;
    }

    /**
     * Convert parsed input to unified format
     *
     * @return string Unified output format
     */
    public function to_string() {
        $seconds       = $this->to_seconds();
        if ( $wp_name = $this->match_wp_native_interval( $seconds ) ) {
            return $wp_name;
        }
        $reverse_quant = array_flip( $this->_multipliers );
        krsort( $reverse_quant );
        $output        = array();
        foreach ( $reverse_quant as $duration => $quant ) {
            if ( $duration > $seconds ) {
                continue;
            }
            $modded = (int)( $seconds / $duration );
            if ( $modded > 0 ) {
                $output[] = $modded . $quant;
                $seconds -= $modded * $duration;
                if ( $seconds <= 0 ) {
                    break;
                }
            }
        }
        return implode( ' ', $output );
    }

    /**
     * Returns seconds interval to native wp name,
     *
     * @param int $seconds Value.
     *
     * @return bool|string False or name.
     */
    public function match_wp_native_interval( $seconds ) {
        if ( empty( $this->_parsed ) ) {
            return false;
        }
        $response = false;
        foreach ( $this->_wp_names as $name => $interval ) {
            if ( $interval['seconds'] === $seconds ) {
                $response = $name;
                break;
            }
        }
        return $response;
    }

    /**
     * Extract time identifiers from input string
     *
     * Given arbitrary string collects known identifiers preceeded by numeric
     * value and counts these. For example, given input '2d 1h 2h' will yield
     * an `array( 'd' => 2, 'h' => 3 )`, that is easy to parse.
     *
     * @param string $input User supplied input
     *
     * @return array Extracted time identifiers
     */
    protected function _match( $input ) {
        $regexp  = '/(\d*\.?\d+)([' .
            implode( '|', array_keys( $this->_multipliers ) ) .
            '])?/';
        $matches = NULL;
        if ( ! preg_match_all( $regexp, $input, $matches ) ) {
            return false;
        }
        $output = array();
        foreach ( $matches[0] as $key => $value ) {
            $quantifier = ( ! empty( $matches[2][$key] ) )
                ? $matches[2][$key]
                : $this->_lowest_quantifier;
            if ( ! isset( $output[$quantifier] ) ) {
                $output[$quantifier] = 0;
            }
            $output[$quantifier] += $matches[1][$key];
        }
        return $output;
    }
}
