<?php

/**
 * Lifestyle Webconsulting GmbH
 *
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is private by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @link       http://www.life-style.de
 */

namespace LifeStyle\Tools\MfaBundle\Api\Mfa\Validate;

use LifeStyle\Tools\MfaBundle\Api\Manager as MfaApiManager;
use LifeStyle\Tools\MfaBundle\MfaValidator;

/**
 * Class Index
 * @package LifeStyle\Tools\MfaBundle\Api\Mfa\Create
 */
class Index implements MfaValidator
{
    /**
     * @var array
     */
    private $facSetter;

    /**
     * @var MfaApiManager
     */
    private $mfaApiM;

    /**
     * Factory constructor.
     * @param MfaApiManager $mfaApiM
     */
    public function __construct(MfaApiManager $mfaApiM)
    {
        $this->mfaApiM = $mfaApiM;
    }

    /**
     * @param string $secret
     * @return array
     */
    public function getOTP($secret)
    {
        $digits = $this->mfaApiM->mfa()->configuration()->getKeyLength();
        $period = $this->mfaApiM->mfa()->configuration()->getPeriod();
        $offset = $this->mfaApiM->mfa()->configuration()->getOffset();

        if (strlen($secret) < 16 || strlen($secret) % 8 != 0) {
            return ['err' => 'length of secret must be a multiple of 8, and at least 16 characters'];
        }
        if (preg_match('/[^a-z2-7]/i', $secret) === 1) {
            return ['err' => 'secret contains non-base32 characters'];
        }
        if ($digits < 6 || $digits > 8) {
            return ['err' => 'digits must be 6, 7 or 8'];
        }
        $seed = $this->base32Decode($secret);
        $time = str_pad(pack('N', intval(time() / $period) + $offset), 8, "\x00", STR_PAD_LEFT);
        $hash = hash_hmac('sha1', $time, $seed, false);
        $otp = (hexdec(substr($hash, hexdec($hash[39]) * 2, 8)) & 0x7fffffff) % pow(10, $digits);

        return sprintf("%'0{$digits}u", $otp);
    }

    /**
     * @param string $in
     * @return string
     */
    private function base32Decode($in)
    {
        $out = '';
        $l = strlen($in);
        $n = $bs = 0;
        for ($i = 0; $i < $l; $i++) {
            $n <<= 5;
            $n += stripos($this->mfaApiM->mfa()->configuration()->getBase32Map(), $in[$i]);
            $bs = ($bs + 5) % 8;
            @$out .= $bs < 5 ? chr(($n & (255 << $bs)) >> $bs) : null;
        }
        return $out;
    }
}
