/********************************************************************************
 * Copyright (c) 2025-2026 ZF Friedrichshafen AG
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License Version 2.0 which is available at
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contributors: 
 *   Erik Verhoeven - initial API and implementation 
 ********************************************************************************/

#ifdef _MSC_VER
#pragma push_macro("interface")
#undef interface
#define NOMINMAX
#include <Windows.h>
#ifdef GetClassInfo
    #undef GetClassInfo
#endif
#pragma pop_macro("interface")
#endif

#include "source.h"
#include "codepos.h"
#include "exception.h"
#include "token.h"
#include <fstream>
#include <filesystem>
#include <clocale>
#include <locale>
#include <codecvt>
#include <cuchar>

CSource::CSource()
{}

CSource::CSource(const std::filesystem::path& rpath)
{
    ReadFile(rpath);
}

CSource::CSource(const char* szCode)
{
    if (!szCode) throw CCompileException("Cannot set code");

    m_ssSource = szCode;
    m_path = std::filesystem::current_path() / "unknown.idl";
}

CSource::CSource(CSource&& rSource) noexcept: m_path(std::move(rSource.m_path)), m_ssSource(std::move(rSource.m_ssSource))
{}

CSource& CSource::operator=(CSource&& rSource) noexcept
{
    m_path = std::move(rSource.m_path);
    m_ssSource = std::move(rSource.m_ssSource);
    return *this;
}

const std::filesystem::path& CSource::GetPathRef() const
{
    return m_path;
}

const std::string& CSource::GetCodeRef() const
{
    return m_ssSource;
}

void CSource::ReadFile(const std::filesystem::path& rpath)
{
    // Set the locale to work with UTF8
    ::setlocale(LC_ALL, "en_US.utf8");

    if (rpath.empty())
        throw CCompileException("File name missing");

    // Store the file name
    m_path = std::filesystem::absolute(rpath);

    // Open the file
    std::ifstream fstream(m_path, std::ios_base::in | std::ios::binary);

    // Is the file opened
    if (!fstream.is_open())
        throw CCompileException(m_path, CToken(), "Cannot open file");

    // Get the size of the file
    fstream.seekg(0, std::ios::end);
    size_t nSize = fstream.tellg();

    // Allocate space for the content
    std::unique_ptr<uint8_t[]> ptruiRawContent = std::make_unique<uint8_t[]>(nSize);
    // Ignore cppcheck warning; normally the returned pointer should always have a value at this stage (otherwise an
    // exception was triggered).
    // cppcheck-suppress knownConditionTrueFalse
    if (!ptruiRawContent)
        throw CCompileException(m_path, CToken(), "Cannot allocate buffer for reading the file");

    // Read the file
    fstream.seekg(0);
    fstream.read(reinterpret_cast<char*>(ptruiRawContent.get()), nSize);
    if (static_cast<size_t>(fstream.gcount()) != nSize)
        throw CCompileException(m_path, CToken(), "Could not read complete file");

    // Determine the BOM
    if (nSize >= 3 && ptruiRawContent[0] == 0xEF && ptruiRawContent[1] == 0xBB && ptruiRawContent[2] == 0xBF)
    {
        // UTF8 BOM - assign the string directly to the member
        if (nSize > 3)
            m_ssSource = std::string(reinterpret_cast<const char*>(ptruiRawContent.get()) + 3, nSize - 3);
    } else
    if (nSize >= 4 && ptruiRawContent[0] == 0x00 && ptruiRawContent[1] == 0x00 && ptruiRawContent[2] == 0xFE && ptruiRawContent[3] == 0xFF)
    {
        // UTF32 BE BOM - conversion needed
        char32_t* szBuffer = reinterpret_cast<char32_t*>(ptruiRawContent.get()) + 1;
        nSize = nSize / sizeof(char32_t) - 1;
        PotentialSwapBuffer(szBuffer, nSize, true);
        m_ssSource = ConvertToUTF8(szBuffer, nSize);
    } else
    if (nSize >= 4 && ptruiRawContent[0] == 0xFF && ptruiRawContent[1] == 0xFE && ptruiRawContent[2] == 0x00 && ptruiRawContent[3] == 0x00)
    {
        // UTF32 LE BOM - conversion needed
        char32_t* szBuffer = reinterpret_cast<char32_t*>(ptruiRawContent.get()) + 1;
        nSize = nSize / sizeof(char32_t) - 1;
        PotentialSwapBuffer(szBuffer, nSize, false);
        m_ssSource = ConvertToUTF8(szBuffer, nSize);
    } else
    if (nSize >= 2 && ptruiRawContent[0] == 0xFE && ptruiRawContent[1] == 0xFF)
    {
        // UTF16 BE BOM - conversion needed
        char16_t* szBuffer = reinterpret_cast<char16_t*>(ptruiRawContent.get()) + 1;
        nSize = nSize / sizeof(char16_t) - 1;
        PotentialSwapBuffer(szBuffer, nSize, true);
        m_ssSource = ConvertToUTF8(szBuffer, nSize);
    } else
    if (nSize >= 2 && ptruiRawContent[0] == 0xFF && ptruiRawContent[1] == 0xFE)
    {
        // UTF16 LE BOM - conversion needed
        char16_t* szBuffer = reinterpret_cast<char16_t*>(ptruiRawContent.get()) + 1;
        nSize = nSize / sizeof(char16_t) - 1;
        PotentialSwapBuffer(szBuffer, nSize, false);
        m_ssSource = ConvertToUTF8(szBuffer, nSize);
    } else
    {
        // No BOM - ANSI character set assumed
        m_ssSource = std::string(reinterpret_cast<const char*>(ptruiRawContent.get()), nSize);
    }
}

constexpr bool CSource::IsSystemBigEndian()
{
    // Use a constant value to check for the byte order
    // False cppcheck warning; uiVal is seen as unused. This is not the case. Suppress warning
    // cppcheck-suppress unusedStructMember
    const union {uint16_t uiVal; uint8_t rguiBytes[2];} uTest{0x0011};

    // Check for big-endian byte order.
    return uTest.rguiBytes[0] == 0x00 && uTest.rguiBytes[1] == 0x11;
}

constexpr bool CSource::IsSystemLittleEndian()
{
    // Use a constant value to check for the byte order
    // False cppcheck warning; uiVal is seen as unused. This is not the case. Suppress warning
    // cppcheck-suppress unusedStructMember
    const union {uint16_t uiVal; uint8_t rguiBytes[2];} uTest{0x0011};

    // Check for big-endian byte order.
    return uTest.rguiBytes[0] == 0x11 && uTest.rguiBytes[1] == 0x00;
}

std::string CSource::ConvertToUTF8(const char16_t* szBuffer, size_t nSize)
{
    if (!szBuffer) return std::string();

    // Return the result.
    return sdv::MakeUtf8String(szBuffer, nSize);
}

std::string CSource::ConvertToUTF8(const char32_t* szBuffer, size_t nSize)
{
    if (!szBuffer) return std::string();

    // Return the result.
    return sdv::MakeUtf8String(szBuffer, nSize);
}
