<?php

namespace Elliptic\Curve\MontCurve;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
use BN\BN;

if ( ! class_exists( 'Elliptic\Curve\MontCurve\Point' ) ) {
    class Point extends \Elliptic\Curve\BaseCurve\Point
    {
        public $x;
        public $z;

        function __construct($curve, $x, $z)
        {
            parent::__construct($curve, "projective");
            if( $x == null && $z == null )
            {
                $this->x = $this->curve->one;
                $this->z = $this->curve->zero;
            }
            else
            {
                $this->x = new BN($x, 16);
                $this->z = new BN($z, 16);
                if( !$this->x->red )
                    $this->x = $this->x->toRed($this->curve->red);
                if( !$this->z->red )
                    $this->z = $this->z->toRed($this->curve->red);
            }
        }

        public function precompute($power = null) {
            // No-op
        }

        protected function _encode($compact) {
            return $this->getX()->toArray("be", $this->curve->p->byteLength());
        }

        public static function fromJSON($curve, $obj) {
            return new Point($curve, $obj[0], isset($obj[1]) ? $obj[1] : $curve->one);
        }

        public function inspect()
        {
            if( $this->isInfinity() )
                return "<EC Point Infinity>";
            return "<EC Point x: " . $this->x->fromRed()->toString(16, 2) .
                " z: " . $this->z->fromRed()->toString(16, 2) . ">";
        }

        public function isInfinity() {
            // XXX This code assumes that zero is always zero in red
            return $this->z->isZero();
        }

        public function dbl()
        {
            // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#doubling-dbl-1987-m-3
            // 2M + 2S + 4A

            // A = X1 + Z1
            $a = $this->x->redAdd($this->z);
            // AA = A^2
            $aa = $a->redSqr();
            // B = X1 - Z1
            $b = $this->x->redSub($this->z);
            // BB = B^2
            $bb = $b->redSqr();
            // C = AA - BB
            $c = $aa->redSub($bb);
            // X3 = AA * BB
            $nx = $aa->redMul($bb);
            // Z3 = C * (BB + A24 * C)
            $nz = $c->redMul( $bb->redAdd($this->curve->a24->redMul($c)) );
            return $this->curve->point($nx, $nz);
        }

        public function add($p) {
            throw new \Exception('Not supported on Montgomery curve');
        }

        public function diffAdd($p, $diff)
        {
            // http://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html#diffadd-dadd-1987-m-3
            // 4M + 2S + 6A

            // A = X2 + Z2
            $a = $this->x->redAdd($this->z);
            // B = X2 - Z2
            $b = $this->x->redSub($this->z);
            // C = X3 + Z3
            $c = $p->x->redAdd($p->z);
            // D = X3 - Z3
            $d = $p->x->redSub($p->z);
            // DA = D * A
            $da = $d->redMul($a);
            // CB = C * B
            $cb = $c->redMul($b);
            // X5 = Z1 * (DA + CB)^2
            $nx = $diff->z->redMul($da->redAdd($cb)->redSqr());
            // Z5 = X1 * (DA - CB)^2
            $nz = $diff->x->redMul($da->redSub($cb)->redSqr());

            return $this->curve->point($nx, $nz);
        }

        public function mul($k)
        {
            $t = $k->_clone();
            $a = $this; // (N / 2) * Q + Q
            $b = $this->curve->point(null, null); // (N / 2) * Q
            $c = $this; // Q

            $bits = array();
            while( !$t->isZero() )
            {
                // TODO: Maybe it is faster to use toString(2)?
                array_push($bits, $t->andln(1));
                $t->iushrn(1);
            }

            for($i = count($bits) - 1; $i >= 0; $i--)
            {
                if( $bits[$i] === 0 )
                {
                    // N * Q + Q = ((N / 2) * Q + Q)) + (N / 2) * Q
                    $a = $a->diffAdd($b, $c);
                    // N * Q = 2 * ((N / 2) * Q + Q))
                    $b = $b->dbl();
                }
                else
                {
                    // N * Q = ((N / 2) * Q + Q) + ((N / 2) * Q)
                    $b = $a->diffAdd($b, $c);
                    // N * Q + Q = 2 * ((N / 2) * Q + Q)
                    $a = $a->dbl();
                }
            }

            return $b;
        }

        public function eq($other) {
            return $this->getX()->cmp($other->getX()) === 0;
        }

        public function normalize()
        {
            $this->x = $this->x->redMul($this->z->redInvm());
            $this->z = $this->curve->one;
            return $this;
        }

        public function getX() {
            $this->normalize();
            return $this->x->fromRed();
        }
    }
}
?>
