// *****************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under    *
// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0          *
// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
// *****************************************************************************

#include "multi_rename.h"
#include <zen/string_tools.h>

using namespace zen;
using namespace fff;


namespace
{
std::wstring_view findLongestSubstring(const std::vector<std::wstring_view>& strings)
{
    if (strings.empty())
        return {};

    const std::wstring_view strMin = *std::min_element(strings.begin(), strings.end(),
    /**/[](const std::wstring_view lhs, const std::wstring_view rhs) { return lhs.size() < rhs.size(); });

    for (size_t sz = strMin.size(); sz > 0; --sz) //iterate over size, descending
        for (size_t i = 0; i + sz <= strMin.size(); ++i)
        {
            const std::wstring_view substr(strMin.data() + i, sz);
            //perf: duplicate substrings, especially für size = 1?

            const bool isCommon = [&]
            {
                for (const std::wstring_view str : strings)
                    if (str.data() != strMin.data()) //sufficient check: an extension of strMin necessarily contains "substr" anyway
                        if (!contains(str, substr))
                            return false;
                return true;
            }();

            if (isCommon)
                return substr; //*first* occuring substring of maximum size
        }

    return {};
}


struct StringPart
{
    std::vector<std::wstring_view> diff; //may be empty, but only at beginning (or between filename/extension parts)
    std::wstring_view common;            //may be empty, but only at end       (dito)
};

std::vector<StringPart> getStringParts(std::vector<std::wstring_view>&& strings)
{
    std::wstring_view substr = findLongestSubstring(strings);
    if (!substr.empty())
    {
        std::vector<std::wstring_view> head;
        std::vector<std::wstring_view> tail;

        for (const std::wstring_view str : strings)
        {
            head.push_back(beforeFirst(str, substr, IfNotFoundReturn::none));
            tail.push_back(afterFirst (str, substr, IfNotFoundReturn::none));
        }

        std::vector<StringPart> np = getStringParts(std::move(head));
        assert(np.empty() || np.back().common.empty()); //otherwise we could construct an even longer substring!

        if (np.empty())
            np.push_back({{}, substr});
        else
            np.back().common = substr;

        const std::vector<StringPart> npTail = getStringParts(std::move(tail));
        assert(npTail.empty() || !npTail.front().diff.empty()); //otherwise we could construct an even longer substring!

        append(np, npTail);
        return np;
    }
    else
    {
        if (std::all_of(strings.begin(), strings.end(), [](const std::wstring_view str) { return str.empty(); }))
        /**/return {};

        return {{std::move(strings), {}}};
    }
}


constexpr wchar_t placeholders[] = //http://xahlee.info/comp/unicode_circled_numbers.html
{
    //L'\u24FF', //⓿ <- rendered bigger than the rest (same for ⓫) on Centos Linux
    L'\u2776', //❶
    L'\u2777', //❷
    L'\u2778', //❸
    L'\u2779', //❹
    L'\u277A', //❺
    L'\u277B', //❻
    L'\u277C', //❼
    L'\u277D', //❽
    L'\u277E', //❾
    L'\u277F', //❿ -> last one is special: represents "all the rest"
};


inline
size_t getPlaceholderIndex(wchar_t c)
{
    static_assert(std::size(placeholders) == 10);
    if (placeholders[0] <= c && c <= placeholders[9])
        return static_cast<size_t>(c - placeholders[0]);

    return static_cast<size_t>(-1);
}
}


bool fff::isRenamePlaceholderChar(wchar_t c) { return getPlaceholderIndex(c) < std::size(placeholders); }


struct fff::RenameBuf
{
    explicit RenameBuf(const std::vector<std::wstring>& s) : strings(s)
    {
        //file extensions deserve special treatment: https://freefilesync.org/forum/viewtopic.php?t=11943#p46453
        std::vector<std::wstring_view> names;
        std::vector<std::wstring_view> extensions; //including "."
        for (const std::wstring& fileName : strings)
        {
            auto it = findLast(fileName.begin(), fileName.end(), L'.');
            names.     push_back(makeStringView(fileName.begin(), it));
            extensions.push_back(makeStringView(it, fileName.end()));
        }

        parts       = getStringParts(std::move(names));
        append(parts, getStringParts(std::move(extensions)));
    }

    std::vector<std::wstring> strings;
    std::vector<StringPart> parts;
};


//e.g. "Season ❶, Episode ❷ - ❸.avi"
std::pair<std::wstring /*phrase*/, SharedRef<const RenameBuf>> fff::getPlaceholderPhrase(const std::vector<std::wstring>& strings)
{
    auto renameBuf = makeSharedRef<const RenameBuf>(strings);

    std::wstring phrase;
    size_t placeIdx = 0;

    for (const StringPart& p : renameBuf.ref().parts)
    {
        if (!p.diff.empty())
        {
            phrase += placeholders[placeIdx++];

            if (placeIdx >= std::size(placeholders))
                break; //represent "all the rest" with last placeholder
        }
        phrase += p.common; //TODO: what if common part incidentally contains placeholder character!?
    }
    return {phrase, renameBuf};
}


const std::vector<std::wstring> fff::resolvePlaceholderPhrase(const std::wstring_view phrase, const RenameBuf& buf)
{
    std::vector<const std::vector<std::wstring_view>*> diffByIdx;

    for (const StringPart& p : buf.parts)
        if (!p.diff.empty())
            diffByIdx.push_back(&p.diff), assert(p.diff.size() == buf.strings.size());

    std::vector<std::wstring> output;

    for (size_t i = 0; i < buf.strings.size(); ++i)
    {
        std::wstring resolved;

        for (const wchar_t c : phrase)
            if (const size_t placeIdx = getPlaceholderIndex(c);
                placeIdx < diffByIdx.size())
            {
                if (placeIdx == std::size(placeholders) - 1) //last placeholder represents "all the rest"
                    resolved.append((*diffByIdx[placeIdx])[i].data(), buf.strings[i].data() + buf.strings[i].size());
                else
                    resolved += (*diffByIdx[placeIdx])[i];
            }
            else
                resolved += c;

        output.push_back(std::move(resolved));
    }

    return output;
}
