<?php

namespace Buckhill\RFC4226;


use Buckhill\OTP\AbstractOTP;

/**
 * Class OTP
 * @package Buckhill\RFC4226
 */
class OTP extends AbstractOTP
{

    /**
     * The secret used for hashing
     *
     * @var null|string
     */
    protected $secret;

    /**
     * Hash algorithm used for the hash_hmac
     *
     * @var string
     */
    protected $algo;

    /**
     * @param null $secret
     * @param string $algo
     */
    public function __construct($secret = null, $algo = 'sha1')
    {
        $this->secret = $secret;
        $this->algo   = $algo;
    }

    /**
     * Retrieves the OTP using given parameters
     *
     * @param int $counter
     * @param int $digits
     * @return string
     */
    public function getOTP($counter = 0, $digits = 6)
    {
        $hash = $this->getHash($this->getSecret(), $this->binary($counter));

        return $this->truncate($hash, $digits);
    }


    /**
     * @param string $name
     * @param        $secret
     * @param int    $counter
     *
     * @return string
     */
    public function getProvisioningUri($name = '', $secret, $counter = 0)
    {
        if(empty($secret) || !is_string($secret))
        {
            throw new \InvalidArgumentException("Invalid value provided for secret. Expecting non-empty string.");
        }

        if(!is_int($counter))
        {
            throw new \InvalidArgumentException(sprintf("Expected integer value for counter, got: %s", gettype($counter)));
        }

        if($counter < 0)
        {
            throw new \InvalidArgumentException("Counter can't be less than 0");
        }

        return sprintf("otpauth://hotp/%s?secret=%s&counter=%d", urlencode($name), $secret, $counter);
    }

    /**
     * @param $otp
     * @param int $position
     * @param int $digits
     * @return bool
     */
    public function verify($otp, $position = 0, $digits = 6)
    {
        $calculated = $this->at($position, $digits);

        return (strcmp($otp, $calculated) === 0);
    }

    /**
     * @param int $position
     * @param int $digits
     * @return string
     */
    public function at($position = 0, $digits = 6)
    {
        return $this->getOTP($position, $digits);
    }

    /**
     * Returns the counter (position) at which the OTP was valid.
     * Useful for synchronization or for verifying an OTP starting from a certain position.
     *
     * @param $otp
     * @param $start
     * @param $window
     * @param int $digits
     * @return bool|int
     */
    public function where($otp, $start, $window, $digits = 6)
    {
        if(!is_int($start) && !is_int($window))
        {
            throw new \InvalidArgumentException("Arguments start and window must be integers, got: %s and %s", gettype($start), gettype($window));
        }

        if($window < 1)
        {
            throw new \InvalidArgumentException("Argument 'window' cannot be lower than int 1. Got %d as value", $window);
        }

        $found = false;

        for($i = $start; $i < $start + $window; $i++)
        {
            if($this->verify($otp, $i, $digits))
            {
                $found = $i;

                break;
            }
        }

        return $found;
    }

    /**
     * @param $secret
     * @param $counter
     * @return string
     */
    public function getHash($secret, $counter)
    {
        return $this->hmac($this->algo, $secret, $counter);
    }
}