<?php

/**
 * This file is part of the moontoast/math library
 *
 * Copyright 2013-2020 Moontoast, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Moontoast\Math;

abstract class AbstractBigNumber implements BigNumberInterface
{
    /**
     * {@inheritdoc}
     */
    public function isEqualTo($number)
    {
        return ($this->compareTo($number) === 0);
    }

    /**
     * {@inheritdoc}
     */
    public function isGreaterThan($number)
    {
        return ($this->compareTo($number) === 1);
    }

    /**
     * {@inheritdoc}
     */
    public function isGreaterThanOrEqualTo($number)
    {
        return ($this->compareTo($number) >= 0);
    }

    /**
     * {@inheritdoc}
     */
    public function isLessThan($number)
    {
        return ($this->compareTo($number) === -1);
    }

    /**
     * {@inheritdoc}
     */
    public function isLessThanOrEqualTo($number)
    {
        return ($this->compareTo($number) <= 0);
    }

    /**
     * {@inheritdoc}
     */
    public function isNegative()
    {
        return ($this->signum() === -1);
    }

    /**
     * {@inheritdoc}
     */
    public function isPositive()
    {
        return ($this->signum() === 1);
    }

    /**
     * {@inheritdoc}
     */
    public function signum()
    {
        if ($this->isGreaterThan(0)) {
            return 1;
        } elseif ($this->isLessThan(0)) {
            return -1;
        }

        return 0;
    }

    /**
     * Converts a number between arbitrary bases (from 2 to 36)
     *
     * @param string|int $number The number to convert
     * @param int $fromBase (optional) The base $number is in; defaults to 10
     * @param int $toBase (optional) The base to convert $number to; defaults to 16
     * @return string
     */
    public static function baseConvert($number, $fromBase = 10, $toBase = 16)
    {
        $number = self::convertToBase10($number, $fromBase);

        return self::convertFromBase10($number, $toBase);
    }

    /**
     * Converts a base-10 number to an arbitrary base (from 2 to 36)
     *
     * @param string|int $number The number to convert
     * @param int $toBase The base to convert $number to
     * @return string
     * @throws \InvalidArgumentException if $toBase is outside the range 2 to 36
     */
    public static function convertFromBase10($number, $toBase)
    {
        if ($toBase < 2 || $toBase > 36) {
            throw new \InvalidArgumentException("Invalid `to base' ({$toBase})");
        }

        $bn = new static($number);
        $number = $bn->abs()->getValue();
        $chars = '0123456789abcdefghijklmnopqrstuvwxyz';
        $outNumber = '';

        while (\bccomp($number, $toBase) >= 0) {
            $remainder = \bcmod($number, $toBase);
            $number = \bcdiv($number, $toBase, 0);
            $outNumber = $chars[(int) $remainder] . $outNumber;
        }

        return $chars[(int) $number] . $outNumber;
    }

    /**
     * Converts a number from an arbitrary base (from 2 to 36) to base 10
     *
     * @param string|int $number The number to convert
     * @param int $fromBase The base $number is in
     * @return string
     * @throws \InvalidArgumentException if $fromBase is outside the range 2 to 36
     */
    public static function convertToBase10($number, $fromBase)
    {
        if ($fromBase < 2 || $fromBase > 36) {
            throw new \InvalidArgumentException("Invalid `from base' ({$fromBase})");
        }

        $number = (string) $number;
        $len = \strlen($number);
        $base10Num = '0';

        for ($i = $len; $i > 0; $i--) {
            $c = \ord($number[$len - $i]);

            if ($c >= \ord('0') && $c <= \ord('9')) {
                $c -= \ord('0');
            } elseif ($c >= \ord('A') && $c <= \ord('Z')) {
                $c -= \ord('A') - 10;
            } elseif ($c >= \ord('a') && $c <= \ord('z')) {
                $c -= \ord('a') - 10;
            } else {
                continue;
            }

            if ($c >= $fromBase) {
                continue;
            }

            $base10Num = \bcadd(\bcmul($base10Num, $fromBase, 0), (string) $c, 0);
        }

        return $base10Num;
    }

    /**
     * Changes the default scale used by all Binary Calculator functions
     *
     * @param int $scale
     * @return void
     */
    public static function setDefaultScale($scale)
    {
        bcscale($scale);
    }
}
