#include "qargs.h"
#include <string>
#include <vector>

namespace NUri {
    namespace NOnStackArgsList {
        struct TQArgNode {
            TStringBuf Name;
            TStringBuf Value;
            TStringBuf All;
        };

        const char* SkipDelimiter(const char* str, const char* end) {
            while (str != end)
                if (*str == '&')
                    ++str;
                else
                    break;
            return str;
        }

        /// return next pos or 0 if error
        const char* ExtractArgData(const char* pos, const char* end, TQArgNode& arg) {
            const char* nameStart = pos;
            const char* nextArg = strchr(pos, '&');
            const char* valueStart = strchr(pos, '=');
            if (valueStart && nextArg && valueStart < nextArg) // a=1& or a=&
            {
                arg.Name = TStringBuf(nameStart, valueStart - nameStart);
                arg.Value = TStringBuf(valueStart + 1, nextArg - valueStart - 1);
                arg.All = TStringBuf(nameStart, nextArg - nameStart);
                return nextArg;
            } else if (valueStart && nextArg && valueStart > nextArg) // a&b=2
            {
                arg.Name = TStringBuf(nameStart, nextArg - nameStart);
                arg.All = arg.Name;
                return nextArg;
            } else if (valueStart && !nextArg) // a=1 or a=
            {
                arg.Name = TStringBuf(nameStart, valueStart - nameStart);
                arg.Value = TStringBuf(valueStart + 1, end - valueStart - 1);
                arg.All = TStringBuf(nameStart, end - nameStart);
                return end;
            } else if (!valueStart && nextArg) // a&b
            {
                arg.Name = TStringBuf(nameStart, nextArg - nameStart);
                arg.All = arg.Name;
                return nextArg;
            } else { // a
                arg.Name = TStringBuf(nameStart, end - nameStart);
                arg.All = arg.Name;
                return end;
            }
        }
    }

    using namespace NOnStackArgsList;

    class TQueryArgProcessing::Pipeline {
    public:
        Pipeline(TQueryArgProcessing& parent, TUri& subject)
            : Parent(parent)
            , Subject(subject)
            , IsDirty(false)
        {
        }

        TQueryArg::EProcessed Process() {
            const TStringBuf& query = Subject.GetField(NUri::TField::FieldQuery);
            if (query.empty())
                return ProcessEmpty();

            const char* start = query.data();
            return Parse(start, start + query.length());
        }

        TQueryArg::EProcessed ProcessEmpty() {
            if (Parent.Flags & TQueryArg::FeatureRemoveEmptyQuery)
                Subject.FldClr(NUri::TField::FieldQuery);

            return TQueryArg::ProcessedOK;
        }

        TQueryArg::EProcessed Parse(const char* str, const char* end) {
            if (str != end)
                Nodes.reserve(8);

            while (str != end) {
                str = SkipDelimiter(str, end);

                TQArgNode current;
                str = ExtractArgData(str, end, current);

                if (!str)
                    return TQueryArg::ProcessedMalformed;

                if (Parent.Flags & TQueryArg::FeatureFilter) {
                    TQueryArg arg = {current.Name, current.Value};
                    if (!Parent.Filter(arg, Parent.FilterData)) {
                        IsDirty = true;
                        continue;
                    }
                }

                Nodes.push_back(current);
            }

            if (Parent.Flags & TQueryArg::FeatureSortByName) {
                std::stable_sort(Nodes.begin(), Nodes.end(), [](auto l, auto r) {
                    return l.Name < r.Name;
                });
                
                IsDirty = true;
            }

            return FinalizeParsing();
        }

        TQueryArg::EProcessed FinalizeParsing() {
            if (!IsDirty)
                return TQueryArg::ProcessedOK;

            bool dirty = Render();

            bool rewrite = Parent.Flags & TQueryArg::FeatureRewriteDirty;
            if (dirty && rewrite)
                Subject.Rewrite();
            return (!dirty || rewrite) ? TQueryArg::ProcessedOK : TQueryArg::ProcessedDirty;
        }

        bool Render() {
            std::string& result = Parent.Buffer;
            result.clear();
            result.reserve(Subject.GetField(NUri::TField::FieldQuery).length());

            bool first = true;
            for (const auto& node: Nodes)
            {
                if (!first)
                    result.append("&");
                result.append(node.All);
                first = false;
            }

            if (result.empty())
                return RenderEmpty();
            else
                return Subject.FldMemSet(NUri::TField::FieldQuery, result);
        }

        bool RenderEmpty() {
            if (Parent.Flags & TQueryArg::FeatureRemoveEmptyQuery)
                Subject.FldClr(NUri::TField::FieldQuery);
            return false;
        }

    private:
        TQueryArgProcessing& Parent;
        TUri& Subject;

        std::vector<TQArgNode> Nodes;
        bool IsDirty;
    };

    TQueryArgProcessing::TQueryArgProcessing(ui32 flags, TQueryArgFilter filter, void* filterData)
        : Flags(flags)
        , Filter(filter)
        , FilterData(filterData)
    {
    }

    TQueryArg::EProcessed TQueryArgProcessing::Process(TUri& uri) {
        Pipeline pipeline(*this, uri);
        return pipeline.Process();
    }
}