/**
 * @file w2cemu.cpp
 * @author Christian Schenk
 * @brief Web2C compatibility functions
 *
 * @copyright Copyright © 2010-2026 Christian Schenk
 *
 * This file is part of the MiKTeX W2CEMU Library.
 *
 * MiKTeX W2CEMU Library is licensed under GNU General Public License version 2
 * or any later version.
 */

#include <cstdlib>

#if defined(MIKTEX_UNIX)
#include <sys/time.h>
#endif

#include <iostream>

#include <fmt/format.h>
#include <fmt/ostream.h>

#include <miktex/Configuration/ConfigNames>
#include <miktex/Core/BufferSizes>
#include <miktex/Core/Directory>
#include <miktex/TeXAndFriends/TeXApp>
#include <miktex/TeXAndFriends/WebAppInputLine>
#include <miktex/Version>

#include <string>

#include "internal.h"

using namespace std;

using namespace MiKTeX;
using namespace MiKTeX::Configuration;
using namespace MiKTeX::Core;
using namespace MiKTeX::TeXAndFriends;
using namespace MiKTeX::Util;

namespace {
    PathName outputDirectory;
}

static void TranslateModeString(const char* modeString, FileMode& mode, FileAccess& access, bool& isTextFile)
{
    if (Utils::Equals(modeString, "r"))
    {
        mode = FileMode::Open;
        access = FileAccess::Read;
        isTextFile = true;
    }
    else if (Utils::Equals(modeString, "w"))
    {
        mode = FileMode::Create;
        access = FileAccess::Write;
        isTextFile = true;
    }
    else if (Utils::Equals(modeString, "rb"))
    {
        mode = FileMode::Open;
        access = FileAccess::Read;
        isTextFile = false;
    }
    else if (Utils::Equals(modeString, "wb"))
    {
        mode = FileMode::Create;
        access = FileAccess::Write;
        isTextFile = false;
    }
    else if (Utils::Equals(modeString, "ab"))
    {
        mode = FileMode::Append;
        access = FileAccess::Write;
        isTextFile = false;
    }
    else
    {
        MIKTEX_UNEXPECTED();
    }
}

static FILE* TryFOpen(const char* path, const char* modeString)
{
    shared_ptr<Session> session = MIKTEX_SESSION();
    FileMode mode(FileMode::Open);
    FileAccess access(FileAccess::Read);
    bool isTextFile;
    TranslateModeString(modeString, mode, access, isTextFile);
    return session->TryOpenFile(PathName(path), mode, access, isTextFile);
}

int Web2C::OpenInput(FILE** ppfile, kpse_file_format_type format, const char* modeString)
{
    MIKTEX_ASSERT(WebAppInputLine::GetWebAppInputLine() != nullptr);
    PathName fileName(WebAppInputLine::GetWebAppInputLine()->GetNameOfFile());
    char* path = miktex_kpathsea_find_file(kpse_def, fileName.GetData(), format, 0);
    if (path == nullptr)
    {
        return 0;
    }
    try
    {
        *ppfile = TryFOpen(path, modeString);
    }
    catch (const exception&)
    {
        MIKTEX_FREE(path);
        throw;
    }
    if (*ppfile != nullptr)
    {
        MIKTEX_ASSERT(WebAppInputLine::GetWebAppInputLine() != nullptr);
        WebAppInputLine::GetWebAppInputLine()->SetNameOfFile(PathName(path));
    }
    MIKTEX_FREE(path);
    return *ppfile == nullptr ? 0 : 1;
}

void Web2C::RecordFileName(const char* path, FileAccess access)
{
    shared_ptr<Session> session = MIKTEX_SESSION();
    if (miktex_web2c_recorder_enabled)
    {
        session->StartFileInfoRecorder();
    }
    session->RecordFileInfo(PathName(path), access);
}

void miktex_web2c_record_file_name(const char* path, int reading)
{
    Web2C::RecordFileName(path, reading ? FileAccess::Read : FileAccess::Write);
}

void Web2C::ChangeRecorderFileName(const char* fileName)
{
    shared_ptr<Session> session = MIKTEX_SESSION();
    PathName path(GetOutputDirectory() / fileName);
    path.AppendExtension(".fls");
    session->SetRecorderPath(path);
}

void miktex_web2c_change_recorder_file_name(const char* path)
{
    Web2C::ChangeRecorderFileName(path);
}

void Web2C::SetOutputDirectory(const PathName& path)
{
    miktex_web2c_set_output_directory(path.GetData());
}

void miktex_web2c_set_output_directory(const char* path)
{
    shared_ptr<Session> session = MIKTEX_SESSION();
    outputDirectory = path;
    outputDirectory.MakeFullyQualified();
    if (!Directory::Exists(outputDirectory))
    {
        if (session->GetConfigValue(MIKTEX_CONFIG_SECTION_TEXANDFRIENDS, MIKTEX_CONFIG_VALUE_CREATEOUTPUTDIRECTORY).GetString() == "t")
        {
            Directory::Create(outputDirectory);
        }
        else
        {
            MIKTEX_FATAL_CRT_ERROR_2(T_("The specified directory does not exist."), "directory", outputDirectory.ToString());
        }
    }
#if 0 // TODO
    if (auxDirectory[0] == 0)
    {
        auxDirectory = outputDirectory;
    }
#endif
    session->AddInputDirectory(outputDirectory, true);
    auto app = WebAppInputLine::GetWebAppInputLine();
    if (app == nullptr)
    {
        return;
    }
    app->SetOutputDirectory(outputDirectory);
}

PathName Web2C::GetOutputDirectory()
{
    auto dir = miktex_web2c_get_output_directory();
    if (dir == nullptr)
    {
        return PathName();
    }
    return PathName(dir);
}

const char* miktex_web2c_get_output_directory()
{
    auto app = WebAppInputLine::GetWebAppInputLine();
    if (app != nullptr)
    {
        outputDirectory = app->GetOutputDirectory();
    }
    return outputDirectory.Empty() ? nullptr : outputDirectory.GetData();
}

void Web2C::GetSecondsAndMicros(int* seconds, int* micros)
{
#if defined(MIKTEX_WINDOWS)
    SYSTEMTIME systemTime;
    GetSystemTime(&systemTime);
    FILETIME fileTime;
    if (!SystemTimeToFileTime(&systemTime, &fileTime))
    {
        MIKTEX_FATAL_WINDOWS_ERROR("SystemTimeToFileTime");
    }
    ULARGE_INTEGER ularge;
    ularge.LowPart = fileTime.dwLowDateTime;
    ularge.HighPart = fileTime.dwHighDateTime;
    ULONGLONG epoch = 116444736000000000;
    *seconds = static_cast<int>((ularge.QuadPart - epoch) / 10000000);
    *micros = systemTime.wMilliseconds * 1000;
#else
    struct timeval tv;
    gettimeofday(&tv, nullptr);
    *seconds = tv.tv_sec;
    *micros = tv.tv_usec;
#endif
}

/* Implementation found in texk/web2c/lib/zround.c
   zround.c: round R to the nearest whole number.  This is supposed to
   implement the predefined Pascal round function.  Public domain. */
integer miktex_zround(double r)
{
    integer i;

    /* R can be outside the range of an integer if glue is stretching or
       shrinking a lot.  We can't do any better than returning the largest
       or smallest integer possible in that case.  It doesn't seem to make
       any practical difference.  Here is a sample input file which
       demonstrates the problem, from phil@cs.arizona.edu:
          \documentstyle{article}
          \begin{document}
          \begin{flushleft}
          $\hbox{} $\hfill
          \filbreak
          \eject

       djb@silverton.berkeley.edu points out we should testing against
       TeX's largest or smallest integer (32 bits), not the machine's.  So
       we might as well use a floating-point constant, and avoid potential
       compiler bugs (also noted by djb, on BSDI).  */
    if (r > 2147483647.0)
        i = 2147483647;
    /* should be ...8, but atof bugs are too common */
    else if (r < -2147483647.0)
        i = -2147483647;
    /* Admittedly some compilers don't follow the ANSI rules of casting
       meaning truncating toward zero; but it doesn't matter enough to do
       anything more complicated here.  */
    else if (r >= 0.0)
        i = (integer)(r + 0.5);
    else
        i = (integer)(r - 0.5);

    return i;
}

void miktex_uexit(int status)
{
    int final_code;
    if (status == 0)
    {
        final_code = EXIT_SUCCESS;
    }
    else if (status == 1)
    {
        final_code = EXIT_FAILURE;
    }
    else
    {
        final_code = status;
    }
    throw final_code;
}

void miktex_setupboundvariable(integer* var, const char* varName, integer dflt)
{
    shared_ptr<Session> session = MIKTEX_SESSION();
    int ret = session->GetConfigValue(MIKTEX_CONFIG_SECTION_NONE, varName, ConfigValue(dflt)).GetInt();
    if (ret >= 0)
    {
        *var = ret;
    }
}

void miktex_usagehelp(const char** lines, const char* bugEmail)
{
    for (; *lines != nullptr; ++lines)
    {
        cout << *lines << "\n";
    }
    cout << "\n";
    if (bugEmail == nullptr)
    {
        cout << "Visit https://miktex.org for bug reports." << "\n";
    }
    else
    {
        cout << fmt::format("Email bug reports to {0}.", bugEmail) << "\n";
    }
    cout.flush();
    throw 0;
}

char* Web2C::GetCurrentFileName()
{
    MIKTEX_ASSERT(WebAppInputLine::GetWebAppInputLine() != nullptr);
    return xstrdup(WebAppInputLine::GetWebAppInputLine()->GetFoundFileFq().GetData());
}

int Web2C::RunSystemCommand(const char* cmd)
{
    int exitCode;
    return (int)TeXApp::GetTeXApp()->Write18(cmd, exitCode);
}

boolean miktex_web2c_recorder_enabled = 0;
char* miktex_web2c_fullnameoffile = nullptr;
