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

#include <cassert>
#include "Header Files/CalcEngine.h"
#include "CalculatorResource.h"

using namespace std;
using namespace CalcEngine;

/**************************************************************************/
/*** Global variable declarations and initializations                   ***/
/**************************************************************************/

static constexpr int DEFAULT_MAX_DIGITS = 32;
static constexpr int DEFAULT_PRECISION = 32;
static constexpr int32_t DEFAULT_RADIX = 10;

static constexpr wchar_t DEFAULT_DEC_SEPARATOR = L'.';
static constexpr wchar_t DEFAULT_GRP_SEPARATOR = L',';
static constexpr wstring_view DEFAULT_GRP_STR = L"3;0";
static constexpr wstring_view DEFAULT_NUMBER_STR = L"0";

// Read strings for keys, errors, trig types, etc.
// These will be copied from the resources to local memory.

unordered_map<wstring, wstring> CCalcEngine::s_engineStrings;

void CCalcEngine::LoadEngineStrings(CalculationManager::IResourceProvider& resourceProvider)
{
    for (const auto& sid : g_sids)
    {
        auto locKey = wstring{ sid };
        auto locString = resourceProvider.GetCEngineString(locKey);
        if (!locString.empty())
        {
            s_engineStrings[locKey] = locString;
        }
    }
}

//////////////////////////////////////////////////
//
// InitialOneTimeOnlyNumberSetup
//
//////////////////////////////////////////////////
void CCalcEngine::InitialOneTimeOnlySetup(CalculationManager::IResourceProvider& resourceProvider)
{
    LoadEngineStrings(resourceProvider);

    // we must now set up all the ratpak constants and our arrayed pointers
    // to these constants.
    ChangeBaseConstants(DEFAULT_RADIX, DEFAULT_MAX_DIGITS, DEFAULT_PRECISION);
}

//////////////////////////////////////////////////
//
// CCalcEngine::CCalcEngine
//
//////////////////////////////////////////////////
CCalcEngine::CCalcEngine(
    bool fPrecedence,
    bool fIntegerMode,
    CalculationManager::IResourceProvider* const pResourceProvider,
    __in_opt ICalcDisplay* pCalcDisplay,
    __in_opt shared_ptr<IHistoryDisplay> pHistoryDisplay)
    : m_fPrecedence(fPrecedence)
    , m_fIntegerMode(fIntegerMode)
    , m_pCalcDisplay(pCalcDisplay)
    , m_resourceProvider(pResourceProvider)
    , m_nOpCode(0)
    , m_nPrevOpCode(0)
    , m_bChangeOp(false)
    , m_bRecord(false)
    , m_bSetCalcState(false)
    , m_input(DEFAULT_DEC_SEPARATOR)
    , m_nFE(FMT_FLOAT)
    , m_memoryValue{ make_unique<Rational>() }
    , m_holdVal{}
    , m_currentVal{}
    , m_lastVal{}
    , m_parenVals{}
    , m_precedenceVals{}
    , m_bError(false)
    , m_bInv(false)
    , m_bNoPrevEqu(true)
    , m_radix(DEFAULT_RADIX)
    , m_precision(DEFAULT_PRECISION)
    , m_cIntDigitsSav(DEFAULT_MAX_DIGITS)
    , m_decGrouping()
    , m_numberString(DEFAULT_NUMBER_STR)
    , m_nTempCom(0)
    , m_openParenCount(0)
    , m_nOp()
    , m_nPrecOp()
    , m_precedenceOpCount(0)
    , m_nLastCom(0)
    , m_angletype(ANGLE_DEG)
    , m_numwidth(QWORD_WIDTH)
    , m_HistoryCollector(pCalcDisplay, pHistoryDisplay, DEFAULT_DEC_SEPARATOR)
    , m_groupSeparator(DEFAULT_GRP_SEPARATOR)
{
    InitChopNumbers();

    m_dwWordBitWidth = DwWordBitWidthFromeNumWidth(m_numwidth);

    m_maxTrigonometricNum = RationalMath::Pow(10, 100);

    SetRadixTypeAndNumWidth(DEC_RADIX, m_numwidth);
    SettingsChanged();
    DisplayNum();
}

void CCalcEngine::InitChopNumbers()
{
    // these rat numbers are set only once and then never change regardless of
    // base or precision changes
    assert(m_chopNumbers.size() >= 4);
    m_chopNumbers[0] = Rational{ rat_qword };
    m_chopNumbers[1] = Rational{ rat_dword };
    m_chopNumbers[2] = Rational{ rat_word };
    m_chopNumbers[3] = Rational{ rat_byte };

    // initialize the max dec number you can support for each of the supported bit lengths
    // this is basically max num in that width / 2 in integer
    assert(m_chopNumbers.size() == m_maxDecimalValueStrings.size());
    for (size_t i = 0; i < m_chopNumbers.size(); i++)
    {
        auto maxVal = m_chopNumbers[i] / 2;
        maxVal = RationalMath::Integer(maxVal);

        m_maxDecimalValueStrings[i] = maxVal.ToString(10, FMT_FLOAT, m_precision);
    }
}

// Gets the number in memory for UI to keep it persisted and set it again to a different instance
// of CCalcEngine. Otherwise it will get destructed with the CalcEngine
unique_ptr<Rational> CCalcEngine::PersistedMemObject()
{
    return move(m_memoryValue);
}

void CCalcEngine::PersistedMemObject(Rational const& memObject)
{
    m_memoryValue = make_unique<Rational>(memObject);
}

void CCalcEngine::SettingsChanged()
{
    wchar_t lastDec = m_decimalSeparator;
    wstring decStr = m_resourceProvider->GetCEngineString(L"sDecimal");
    m_decimalSeparator = decStr.empty() ? DEFAULT_DEC_SEPARATOR : decStr.at(0);
    // Until it can be removed, continue to set ratpak decimal here
    SetDecimalSeparator(m_decimalSeparator);

    wchar_t lastSep = m_groupSeparator;
    wstring sepStr = m_resourceProvider->GetCEngineString(L"sThousand");
    m_groupSeparator = sepStr.empty() ? DEFAULT_GRP_SEPARATOR : sepStr.at(0);

    auto lastDecGrouping = m_decGrouping;
    wstring grpStr = m_resourceProvider->GetCEngineString(L"sGrouping");
    m_decGrouping = DigitGroupingStringToGroupingVector(grpStr.empty() ? DEFAULT_GRP_STR : grpStr);

    bool numChanged = false;

    // if the grouping pattern or thousands symbol changed we need to refresh the display
    if (m_decGrouping != lastDecGrouping || m_groupSeparator != lastSep)
    {
        numChanged = true;
    }

    // if the decimal symbol has changed we always do the following things
    if (m_decimalSeparator != lastDec)
    {
        // Re-initialize member variables' decimal point.
        m_input.SetDecimalSymbol(m_decimalSeparator);
        m_HistoryCollector.SetDecimalSymbol(m_decimalSeparator);

        // put the new decimal symbol into the table used to draw the decimal key
        s_engineStrings[SIDS_DECIMAL_SEPARATOR] = m_decimalSeparator;

        // we need to redraw to update the decimal point button
        numChanged = true;
    }

    if (numChanged)
    {
        DisplayNum();
    }
}

wchar_t CCalcEngine::DecimalSeparator() const
{
    return m_decimalSeparator;
}
