// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "Header Files/CalcEngine.h"

using namespace CalcEngine;
using namespace CalcEngine::RationalMath;

// Routines to perform standard operations &|^~<<>>+-/*% and pwr.
CalcEngine::Rational CCalcEngine::DoOperation(int operation, CalcEngine::Rational const& lhs, CalcEngine::Rational const& rhs)
{
    // Remove any variance in how 0 could be represented in rat e.g. -0, 0/n, etc.
    auto result = (lhs != 0 ? lhs : 0);

    try
    {
        switch (operation)
        {
        case IDC_AND:
            result &= rhs;
            break;

        case IDC_OR:
            result |= rhs;
            break;

        case IDC_XOR:
            result ^= rhs;
            break;

        case IDC_NAND:
            result = (result & rhs) ^ m_chopNumbers[m_numwidth];
            break;

        case IDC_NOR:
            result = (result | rhs) ^ m_chopNumbers[m_numwidth];
            break;

        case IDC_RSHF:
        {
            if (m_fIntegerMode && result >= m_dwWordBitWidth) // Lsh/Rsh >= than current word size is always 0
            {
                throw CALC_E_NORESULT;
            }

            uint64_t w64Bits = rhs.ToUInt64_t();
            bool fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;

            Rational holdVal = result;
            result = rhs >> holdVal;

            if (fMsb)
            {
                result = Integer(result);

                auto tempRat = m_chopNumbers[m_numwidth] >> holdVal;
                tempRat = Integer(tempRat);

                result |= tempRat ^ m_chopNumbers[m_numwidth];
            }
            break;
        }
        case IDC_RSHFL:
        {
            if (m_fIntegerMode && result >= m_dwWordBitWidth) // Lsh/Rsh >= than current word size is always 0
            {
                throw CALC_E_NORESULT;
            }

            result = rhs >> result;
            break;
        }
        case IDC_LSHF:
            if (m_fIntegerMode && result >= m_dwWordBitWidth) // Lsh/Rsh >= than current word size is always 0
            {
                throw CALC_E_NORESULT;
            }

            result = rhs << result;
            break;

        case IDC_ADD:
            result += rhs;
            break;

        case IDC_SUB:
            result = rhs - result;
            break;

        case IDC_MUL:
            result *= rhs;
            break;

        case IDC_DIV:
        case IDC_MOD:
        {
            int iNumeratorSign = 1, iDenominatorSign = 1;
            auto temp = result;
            result = rhs;

            if (m_fIntegerMode)
            {
                uint64_t w64Bits = rhs.ToUInt64_t();
                bool fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;

                if (fMsb)
                {
                    result = (rhs ^ m_chopNumbers[m_numwidth]) + 1;

                    iNumeratorSign = -1;
                }

                w64Bits = temp.ToUInt64_t();
                fMsb = (w64Bits >> (m_dwWordBitWidth - 1)) & 1;

                if (fMsb)
                {
                    temp = (temp ^ m_chopNumbers[m_numwidth]) + 1;

                    iDenominatorSign = -1;
                }
            }

            if (operation == IDC_DIV)
            {
                result /= temp;
                if (m_fIntegerMode && (iNumeratorSign * iDenominatorSign) == -1)
                {
                    result = -(Integer(result));
                }
            }
            else
            {
                if (m_fIntegerMode)
                {
                    // Programmer mode, use remrat (remainder after division)
                    result %= temp;

                    if (iNumeratorSign == -1)
                    {
                        result = -(Integer(result));
                    }
                }
                else
                {
                    // other modes, use modrat (modulus after division)
                    result = Mod(result, temp);
                }
            }
            break;
        }

        case IDC_PWR: // Calculates rhs to the result(th) power.
            result = Pow(rhs, result);
            break;

        case IDC_ROOT: // Calculates rhs to the result(th) root.
            result = Root(rhs, result);
            break;

        case IDC_LOGBASEX:
            result = (Log(result) / Log(rhs));
            break;
        }
    }
    catch (uint32_t dwErrCode)
    {
        DisplayError(dwErrCode);

        // On error, return the original value
        result = lhs;
    }

    return result;
}
