#include "jinja2cpp/template.h"
#include "template_impl.h"

#include <fmt/format.h>

#include <fstream>
#include <sstream>

namespace jinja2
{
bool operator==(const Template& lhs, const Template& rhs)
{
    return lhs.IsEqual(rhs);
}

bool operator==(const TemplateW& lhs, const TemplateW& rhs)
{
    return lhs.IsEqual(rhs);
}

template<typename CharT>
auto GetImpl(std::shared_ptr<ITemplateImpl> impl)
{
    return static_cast<TemplateImpl<CharT>*>(impl.get());
}

Template::Template(TemplateEnv* env)
    : m_impl(new TemplateImpl<char>(env))
{

}

Template::~Template() = default;

Result<void> Template::Load(const char* tpl, std::string tplName)
{
    std::string t(tpl);
    auto result = GetImpl<char>(m_impl)->Load(std::move(t), std::move(tplName));
    return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
}

Result<void> Template::Load(const std::string& str, std::string tplName)
{
    auto result = GetImpl<char>(m_impl)->Load(str, std::move(tplName));
    return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
}

Result<void> Template::Load(std::istream& stream, std::string tplName)
{
    std::string t;

    while (stream.good() && !stream.eof())
    {
        char buff[0x10000];
        stream.read(buff, sizeof(buff));
        auto read = stream.gcount();
        if (read)
            t.append(buff, buff + read);
    }

    auto result = GetImpl<char>(m_impl)->Load(std::move(t), std::move(tplName));
    return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
}

Result<void> Template::LoadFromFile(const std::string& fileName)
{
    std::ifstream file(fileName);

    if (!file.good())
        return Result<void>();

    return Load(file, fileName);
}

Result<void> Template::Render(std::ostream& os, const jinja2::ValuesMap& params)
{
    std::string buffer;
    auto result = GetImpl<char>(m_impl)->Render(buffer, params);

    if (!result)
        os.write(buffer.data(), buffer.size());

    return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
}

Result<std::string> Template::RenderAsString(const jinja2::ValuesMap& params)
{
    std::string buffer;
    auto result = GetImpl<char>(m_impl)->Render(buffer, params);
    return !result ? Result<std::string>(std::move(buffer)) : Result<std::string>(nonstd::make_unexpected(std::move(result.get())));;
}

Result<GenericMap> Template::GetMetadata()
{
    return GetImpl<char>(m_impl)->GetMetadata();
}

Result<MetadataInfo<char>> Template::GetMetadataRaw()
{
    return GetImpl<char>(m_impl)->GetMetadataRaw();
}

bool Template::IsEqual(const Template& other) const
{
    return m_impl == other.m_impl;
}

TemplateW::TemplateW(TemplateEnv* env)
    : m_impl(new TemplateImpl<wchar_t>(env))
{

}

TemplateW::~TemplateW() = default;

ResultW<void> TemplateW::Load(const wchar_t* tpl, std::string tplName)
{
    std::wstring t(tpl);
    auto result = GetImpl<wchar_t>(m_impl)->Load(t, std::move(tplName));
    return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
}

ResultW<void> TemplateW::Load(const std::wstring& str, std::string tplName)
{
    auto result = GetImpl<wchar_t>(m_impl)->Load(str, std::move(tplName));
    return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
}

ResultW<void> TemplateW::Load(std::wistream& stream, std::string tplName)
{
    std::wstring t;

    while (stream.good() && !stream.eof())
    {
        wchar_t buff[0x10000];
        stream.read(buff, sizeof(buff));
        auto read = stream.gcount();
        if (read)
            t.append(buff, buff + read);
    }

    auto result = GetImpl<wchar_t>(m_impl)->Load(t, std::move(tplName));
    return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
}

ResultW<void> TemplateW::LoadFromFile(const std::string& fileName)
{
    std::wifstream file(fileName);

    if (!file.good())
        return ResultW<void>();

    return Load(file, fileName);
}

ResultW<void> TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params)
{
    std::wstring buffer;
    auto result = GetImpl<wchar_t>(m_impl)->Render(buffer, params);
    if (!result)
        os.write(buffer.data(), buffer.size());
    return !result ? ResultW<void>() : ResultW<void>(nonstd::make_unexpected(std::move(result.get())));
}

ResultW<std::wstring> TemplateW::RenderAsString(const jinja2::ValuesMap& params)
{
    std::wstring buffer;
    auto result = GetImpl<wchar_t>(m_impl)->Render(buffer, params);

    return !result ? buffer : ResultW<std::wstring>(nonstd::make_unexpected(std::move(result.get())));
}

ResultW<GenericMap> TemplateW::GetMetadata()
{
    return GenericMap();
    // GetImpl<wchar_t>(m_impl)->GetMetadata();
}

ResultW<MetadataInfo<wchar_t>> TemplateW::GetMetadataRaw()
{
    return MetadataInfo<wchar_t>();
    // GetImpl<wchar_t>(m_impl)->GetMetadataRaw();
    ;
}
bool TemplateW::IsEqual(const TemplateW& other) const
{
    return m_impl == other.m_impl;
}

} // namespace jinja2