#include "File.h"

#include "FileLogger.h"
#include "ByteUtils.h"

#include <streambuf>
#include <sstream>

File::File(std::filesystem::path path)
    : mFullPath(path),
      mInHandle(),
      mOutHandle()
{

}

File::~File()
{
    close();
}

std::string File::getExtension() const
{
    return mFullPath.extension().string();
}

std::string File::dumpBinary()
{
    open(AccessMode::Read);

    std::stringstream sstr;
    sstr << "Count | Binary | Decimal | ASCII \n";
    unsigned count = 0;
    while(mInHandle->peek() != EOF)
    {
        const unsigned char val = static_cast<unsigned char>(mInHandle->get());
        const unsigned char ascii_val = std::isalpha(val) ? val : '.';
        sstr << count << " | " << ByteUtils::toString(val) << " | " << static_cast<int>(val) << " | " << ascii_val << '\n';
        if (count < 0 && count % 10 == 0)
        {
            sstr << "\n";
        }
        count++;
    }
    const auto out = sstr.str();
    close();
    return out;
}

std::ifstream* File::getInHandle() const
{
    return mInHandle.get();
}

std::ofstream* File::getOutHandle() const
{
    return mOutHandle.get();
}

std::optional<unsigned char> File::readNextByte()
{
    if (!mInHandle)
    {
        if (!open(AccessMode::Read))
        {
            return std::nullopt;
        }
    }

    if (mInHandle->good())
    {
        if (auto val = mInHandle->get(); val == EOF)
        {
            return std::nullopt;
        }
        else
        {
            return val;
        }
    }
    else
    {
        return std::nullopt;
    }
}

bool File::open(AccessMode accessMode)
{
    if (mFullPath.is_absolute() && !std::filesystem::exists(mFullPath.parent_path()))
    {
        std::filesystem::create_directories(mFullPath.parent_path());
    }

    if(accessMode == AccessMode::Read)
    {
        auto flags = std::ifstream::in;
        mInHandle = std::make_unique<std::ifstream>();
        mInHandle->open(mFullPath, flags);
        if (!mInHandle->is_open())
        {
            MLOG_ERROR("Failed to open file at :" << mFullPath);
            return false;
        }
    }
    else
    {
        auto flags = std::ofstream::out;
        mOutHandle = std::make_unique<std::ofstream>();
        mOutHandle->open(mFullPath, flags);
        if (!mOutHandle->is_open())
        {
            MLOG_ERROR("Failed to open file at :" << mFullPath);
            return false;
        }
    }
    return true;
}

void File::close()
{
    if(mOutHandle)
    {
        mOutHandle->close();
    }
    if(mInHandle)
    {
        mInHandle->close();
    }
}

FileFormat::Format File::inferFormat() const
{
    const auto extension = getExtension();
    return FileFormat::inferFormat(extension);
}

void File::writeText(const std::string& text)
{
    bool had_to_open{false};
    if (!mOutHandle)
    {
        had_to_open = true;
        if (!open(File::AccessMode::Write))
        {
            return;
        }
    }

    (*mOutHandle) << text;

    if (had_to_open)
    {
        close();
    }
}

std::vector<std::string> File::readLines()
{
    std::vector<std::string> content;

    if (!pathExists())
    {
        return {};
    }

    if(!open(AccessMode::Read))
    {
        return {};
    }

    std::string str;
    while(std::getline(*mInHandle, str))
    {
        content.push_back(str);
    }

    close();
    return content;
}

std::string File::read()
{
    if (!pathExists())
    {
        return {};
    }

    if(!open(AccessMode::Read))
    {
        return {};
    }

    std::stringstream buffer;
    buffer << mInHandle->rdbuf();

    close();
    return buffer.str();
}

std::string File::readText()
{
    if (!pathExists())
    {
        return {};
    }

    if(!open(AccessMode::Read))
    {
        return {};
    }

    std::string str((std::istreambuf_iterator<char>(*mInHandle)),
                     std::istreambuf_iterator<char>());
    return str;
}

bool File::pathExists() const
{
    return std::filesystem::exists(mFullPath);
}
