<?php

namespace Elliptic;

use \Exception;
use BN\BN;

if (!function_exists("random_int")) {
    function random_int($a, $b) {
        return rand($a, $b);
    }
}

class Utils
{
    public static $ASSERT_ENABLED;
    
    public static function toArray($msg, $enc = false)
    {
        if( is_array($msg) )
            return array_slice($msg, 0);

        if( !$msg )
            return array();

        if( !is_string($msg) )
            throw new Exception("Not implemented");

        if( !$enc )
            return array_slice(unpack("C*", $msg), 0);

        if( $enc === "hex" )
            return array_slice(unpack("C*", hex2bin($msg)), 0);

        return $msg;
    }

    public static function toHex($msg)
    {
        if( is_string($msg) )
            return bin2hex($msg);

        if( !is_array($msg) )
            throw new Exception("Not implemented");

        $binary = call_user_func_array("pack", array_merge(["C*"], $msg)); 
        return bin2hex($binary);
    }

    public static function toBin($msg, $enc = false)
    {
        if( is_array($msg) )
            return call_user_func_array("pack", array_merge(["C*"], $msg)); 

        if( $enc === "hex" )
            return hex2bin($msg);

        return $msg;
    }

    public static function encode($arr, $enc)
    {
        if( $enc === "hex" )
            return self::toHex($arr);
        return $arr;
    }

    // Represent num in a w-NAF form
    public static function getNAF($num, $w)
    {
        $naf = array();
        $ws = 1 << ($w + 1);
        $k = clone($num);

        while( $k->cmpn(1) >= 0 )
        {
            if( !$k->isOdd() )
                array_push($naf, 0);
            else
            {
                $mod = $k->andln($ws - 1);
                $z = $mod;
                if( $mod > (($ws >> 1) - 1))
                    $z = ($ws >> 1) - $mod;
                $k->isubn($z);
                array_push($naf, $z);
            }

            // Optimization, shift by word if possible
            $shift = (!$k->isZero() && $k->andln($ws - 1) === 0) ? ($w + 1) : 1;
            for($i = 1; $i < $shift; $i++)
                array_push($naf, 0);
            $k->iushrn($shift);
        }

        return $naf;
    }

    // Represent k1, k2 in a Joint Sparse Form
    public static function getJSF($k1, $k2)
    {
        $jsf = array( array(), array() );
        $k1 = $k1->_clone();
        $k2 = $k2->_clone();
        $d1 = 0;
        $d2 = 0;

        while( $k1->cmpn(-$d1) > 0 || $k2->cmpn(-$d2) > 0 )
        {
            // First phase
            $m14 = ($k1->andln(3) + $d1) & 3;
            $m24 = ($k2->andln(3) + $d2) & 3;
            if( $m14 === 3 )
                $m14 = -1;
            if( $m24 === 3 )
                $m24 = -1;

            $u1 = 0;
            if( ($m14 & 1) !== 0 )
            {
                $m8 = ($k1->andln(7) + $d1) & 7;
                $u1 = ( ($m8 === 3 || $m8 === 5) && $m24 === 2 ) ? -$m14 : $m14;
            }
            array_push($jsf[0], $u1);

            $u2 = 0;
            if( ($m24 & 1) !== 0 )
            {
                $m8 = ($k2->andln(7) + $d2) & 7;
                $u2 = ( ($m8 === 3 || $m8 === 5) && $m14 === 2 ) ? -$m24 : $m24;
            }
            array_push($jsf[1], $u2);

            // Second phase
            if( (2 * $d1) === ($u1 + 1) )
                $d1 = 1 - $d1;
            if( (2 * $d2) === ($u2 + 1) )
                $d2 = 1 - $d2;
            $k1->iushrn(1);
            $k2->iushrn(1);
        }

        return $jsf;
    }

    public static function intFromLE($bytes) {
        return new BN($bytes, 'hex', 'le');
    }

    public static function parseBytes($bytes) {
        if (is_string($bytes))
            return self::toArray($bytes, 'hex');
        return $bytes;
    }

    public static function randBytes($count)
    {
        $res = "";
        for($i = 0; $i < $count; $i++)
            $res .= chr(random_int(0, 255));
        return $res;
    }

    public static function optionAssert(&$array, $key, $value = false, $required = false)
    {
        if( isset($array[$key]) )
            return;
        if( $required )
            throw new Exception("Missing option " . $key);
        $array[$key] = $value;
    }
}

Utils::$ASSERT_ENABLED = ini_get("zend.assertions") === "1";

?>
