#include <stdlib.h>
#include <string>
#include <vector>
#include <set>
#include <Windows.h>
#include <Shlwapi.h>
#include <Shellapi.h>

#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "Shell32.lib")

class cmdline_maker
{
    std::wstring exe_path;
    std::vector<std::wstring> args;

public:
    cmdline_maker(const std::wstring& exe)
        : exe_path(exe)
    {
    }

    ~cmdline_maker(){}

    cmdline_maker& add_arg(const std::wstring& key, const std::wstring& value, wchar_t delimiter = L'=')
    {
        args.push_back(make_arg(key, value, delimiter));
        return *this;
    }

    cmdline_maker& add_arg(const std::wstring& arg)
    {
        args.push_back(arg_quote(arg));
        return *this;
    }

    std::wstring make_up()
    {
        if (exe_path.empty())
        {
            exe_path =__wargv[0];
        }
        std::wstring cmd(path_quote(exe_path));
        for (auto& arg : args)
        {
            cmd += L" ";
            cmd += arg;
        }
        return cmd;
    }

    static std::wstring get_current_exe_path()
    {
        wchar_t path[MAX_PATH];
        GetModuleFileNameW(NULL, path, MAX_PATH);
        return path;
    }

    static std::wstring path_quote(const std::wstring& path)
    {
        wchar_t path_quoted[MAX_PATH];
        wcscpy_s(path_quoted, MAX_PATH, path.c_str());
        ::PathQuoteSpaces(path_quoted);
        return path_quoted;
    }

    static std::wstring arg_quote(const std::wstring& arg)
    {
        if (arg.find_first_of(L" \t\n\v\"") == std::wstring::npos)
        {
            return arg;
        }
        std::wstring arg_quoted(L"\"");
        for (auto it = arg.begin();; ++it)
        {
            unsigned int backslash_count = 0;

            while (it != arg.end() && *it == L'\\')
            {
                ++it;
                ++backslash_count;
            }

            if (it == arg.end())
            {
                arg_quoted.append(backslash_count * 2, L'\\');
                break;
            }
            else if (*it == L'\"')
            {
                arg_quoted.append(backslash_count * 2 + 1, L'\\');
                arg_quoted.push_back(*it);
            }
            else
            {
                arg_quoted.append(backslash_count, L'\\');
                arg_quoted.push_back(*it);
            }
        }
        arg_quoted.push_back(L'\"');
        return arg_quoted;
    }

    static std::wstring make_arg(const std::wstring& key, const std::wstring& value, wchar_t delimiter = L'=')
    {
        std::wstring arg = arg_quote(key) + delimiter + arg_quote(value);
        return arg;
    }
};

class cmdline_parser
{
    std::wstring exe_path;
    std::set<std::wstring> args;

public:
    cmdline_parser() { parse(GetCommandLineW()); }
    cmdline_parser(const std::wstring& cmd) { parse(cmd.c_str()); }
    
    std::wstring get_exe_path() { return exe_path; }

    std::wstring get_arg(const std::wstring& key, const wchar_t* delimiter = L"=:")
    {
        for (auto& arg : args)
        {
            if (arg.length() > key.length()
                && arg.substr(0, key.length()) == key
                && wcschr(delimiter, arg[key.length()]) != nullptr)
            {
                return arg.substr(key.length() + 1);
            }
        }
        return std::wstring();
    }

    std::wstring get_arg_i(const std::wstring& key, const wchar_t* delimiter = L"=:")
    {
        for (auto& arg : args)
        {
            if (arg.length() > key.length()
                && _wcsicmp(arg.substr(0, key.length()).c_str(), key.c_str()) == 0
                && wcschr(delimiter, arg[key.length()]) != nullptr)
            {
                return arg.substr(key.length() + 1);
            }
        }
        return std::wstring();
    }

    bool if_arg_exist(const std::wstring& key)
    {
        return args.count(key) > 0;
    }

    bool if_arg_exist_i(const std::wstring& key)
    {
        for (auto& arg : args)
        {
            if (_wcsicmp(arg.c_str(), key.c_str()) == 0)
                return true;
        }
        return false;
    }

protected:
    void parse(const wchar_t* cmd)
    {
        int argc = 0;
        LPWSTR* argv = ::CommandLineToArgvW(cmd, &argc);
        if (argv && argc > 0)
        {
            exe_path = argv[0];

            for (int i = 1; i < argc; ++i)
            {
                args.insert(argv[i]);
            }
        }
        ::LocalFree(argv);
    }
};