//
// Copyright (c) 2017-2020 Advanced Micro Devices, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

#pragma once

#include "VmaUsage.h"

#include <iostream>
#include <fstream>
#include <vector>
#include <memory>
#include <algorithm>
#include <numeric>
#include <array>
#include <type_traits>
#include <utility>
#include <chrono>
#include <string>
#include <limits>

#include <cassert>
#include <cstdlib>
#include <cstdio>
#include <cstdarg>

typedef std::chrono::high_resolution_clock::time_point time_point;
typedef std::chrono::high_resolution_clock::duration duration;

inline float ToFloatSeconds(duration d)
{
    return std::chrono::duration_cast<std::chrono::duration<float>>(d).count();
}

void SecondsToFriendlyStr(float seconds, std::string& out);

template <typename T>
T ceil_div(T x, T y)
{
    return (x+y-1) / y;
}
template <typename T>
inline T round_div(T x, T y)
{
    return (x+y/(T)2) / y;
}

template <typename T>
inline T align_up(T val, T align)
{
    return (val + align - 1) / align * align;
}

struct StrRange
{
    const char* beg;
    const char* end;

    StrRange() { }
    StrRange(const char* beg, const char* end) : beg(beg), end(end) { }
    explicit StrRange(const char* sz) : beg(sz), end(sz + strlen(sz)) { }
    explicit StrRange(const std::string& s) : beg(s.data()), end(s.data() + s.length()) { }

    size_t length() const { return end - beg; }
    void to_str(std::string& out) const { out.assign(beg, end); }
};

inline bool StrRangeEq(const StrRange& lhs, const char* rhsSz)
{
    const size_t rhsLen = strlen(rhsSz);
    return rhsLen == lhs.length() &&
        memcmp(lhs.beg, rhsSz, rhsLen) == 0;
}

inline bool StrRangeToUint(const StrRange& s, uint32_t& out)
{
    char* end = (char*)s.end;
    out = (uint32_t)strtoul(s.beg, &end, 10);
    return end == s.end;
}
inline bool StrRangeToUint(const StrRange& s, uint64_t& out)
{
    char* end = (char*)s.end;
    out = (uint64_t)strtoull(s.beg, &end, 10);
    return end == s.end;
}
inline bool StrRangeToPtr(const StrRange& s, uint64_t& out)
{
    char* end = (char*)s.end;
    out = (uint64_t)strtoull(s.beg, &end, 16);
    return end == s.end;
}
inline bool StrRangeToFloat(const StrRange& s, float& out)
{
    char* end = (char*)s.end;
    out = strtof(s.beg, &end);
    return end == s.end;
}
inline bool StrRangeToBool(const StrRange& s, bool& out)
{
    if(s.end - s.beg == 1)
    {
        if(*s.beg == '1')
        {
            out = true;
        }
        else if(*s.beg == '0')
        {
            out = false;
        }
        else
        {
            return false;
        }
    }
    else
    {
        return false;
    }

    return true;
}
bool StrRangeToPtrList(const StrRange& s, std::vector<uint64_t>& out);

class LineSplit
{
public:
    LineSplit(const char* data, size_t numBytes) :
        m_Data(data),
        m_NumBytes(numBytes),
        m_NextLineBeg(0),
        m_NextLineIndex(0)
    {
    }

    bool GetNextLine(StrRange& out);
    size_t GetNextLineIndex() const { return m_NextLineIndex; }

private:
    const char* const m_Data;
    const size_t m_NumBytes;
    size_t m_NextLineBeg;
    size_t m_NextLineIndex;
};

class CsvSplit
{
public:
    static const size_t RANGE_COUNT_MAX = 32;

    void Set(const StrRange& line, size_t maxCount = RANGE_COUNT_MAX);

    const StrRange& GetLine() const { return m_Line; }

    size_t GetCount() const { return m_Count; }
    StrRange GetRange(size_t index) const 
    {
        if(index < m_Count)
        {
            return StrRange {
                m_Line.beg + m_Ranges[index * 2],
                m_Line.beg + m_Ranges[index * 2 + 1] };
        }
        else
        {
            return StrRange{0, 0};
        }
    }

private:
    StrRange m_Line = { nullptr, nullptr };
    size_t m_Count = 0;
    size_t m_Ranges[RANGE_COUNT_MAX * 2]; // Pairs of begin-end.
};

class CmdLineParser
{
public:
	enum RESULT
	{
		RESULT_OPT,
		RESULT_PARAMETER,
		RESULT_END,
		RESULT_ERROR,
	};

	CmdLineParser(int argc, char **argv);
	CmdLineParser(const char *CmdLine);
	
    void RegisterOpt(uint32_t Id, char Opt, bool Parameter);
	void RegisterOpt(uint32_t Id, const std::string &Opt, bool Parameter);
	
    RESULT ReadNext();
	uint32_t GetOptId();
	const std::string & GetParameter();

private:
	struct SHORT_OPT
	{
		uint32_t Id;
		char Opt;
		bool Parameter;

		SHORT_OPT(uint32_t Id, char Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { }
	};

	struct LONG_OPT
	{
		uint32_t Id;
		std::string Opt;
		bool Parameter;

		LONG_OPT(uint32_t Id, std::string Opt, bool Parameter) : Id(Id), Opt(Opt), Parameter(Parameter) { }
	};

	char **m_argv;
	const char *m_CmdLine;
	int m_argc;
	size_t m_CmdLineLength;
	size_t m_ArgIndex;

	bool ReadNextArg(std::string *OutArg);

	std::vector<SHORT_OPT> m_ShortOpts;
	std::vector<LONG_OPT> m_LongOpts;

	SHORT_OPT * FindShortOpt(char Opt);
	LONG_OPT * FindLongOpt(const std::string &Opt);

	bool m_InsideMultioption;
	std::string m_LastArg;
	size_t m_LastArgIndex;
	uint32_t m_LastOptId;
	std::string m_LastParameter;
};

/*
Parses and stores a sequence of ranges.

Upper range is inclusive.

Examples:

    "1" -> [ {1, 1} ]
    "1,10" -> [ {1, 1}, {10, 10} ]
    "2-6" -> [ {2, 6} ]
    "-8" -> [ {MIN, 8} ]
    "12-" -> [ {12, MAX} ]
    "1-10,12,15-" -> [ {1, 10}, {12, 12}, {15, MAX} ]

TODO: Optimize it: Do sorting and merging while parsing. Do binary search while
reading.
*/
template<typename T>
class RangeSequence
{
public:
    typedef std::pair<T, T> RangeType;

    void Clear() { m_Ranges.clear(); }
    bool Parse(const StrRange& str);

    bool IsEmpty() const { return m_Ranges.empty(); }
    size_t GetCount() const { return m_Ranges.size(); }
    const RangeType* GetRanges() const { return m_Ranges.data(); }

    bool Includes(T number) const;
    
private:
    std::vector<RangeType> m_Ranges;
};

template<typename T>
bool RangeSequence<T>::Parse(const StrRange& str)
{
    m_Ranges.clear();

    StrRange currRange = { str.beg, str.beg };
    while(currRange.beg < str.end)
    {
        currRange.end = currRange.beg + 1;
        // Find next ',' or the end.
        while(currRange.end < str.end && *currRange.end != ',')
        {
            ++currRange.end;
        }

        // Find '-' within this range.
        const char* hyphenPos = currRange.beg;
        while(hyphenPos < currRange.end && *hyphenPos != '-')
        {
            ++hyphenPos;
        }

        // No hyphen - single number like '10'.
        if(hyphenPos == currRange.end)
        {
            RangeType range;
            if(!StrRangeToUint(currRange, range.first))
            {
                return false;
            }
            range.second = range.first;
            m_Ranges.push_back(range);
        }
        // Hyphen at the end, like '10-'.
        else if(hyphenPos + 1 == currRange.end)
        {
            const StrRange numberRange = { currRange.beg, hyphenPos };
            RangeType range;
            if(!StrRangeToUint(numberRange, range.first))
            {
                return false;
            }
            range.second = std::numeric_limits<T>::max();
            m_Ranges.push_back(range);
        }
        // Hyphen at the beginning, like "-10".
        else if(hyphenPos == currRange.beg)
        {
            const StrRange numberRange = { currRange.beg + 1, currRange.end };
            RangeType range;
            range.first = std::numeric_limits<T>::min();
            if(!StrRangeToUint(numberRange, range.second))
            {
                return false;
            }
            m_Ranges.push_back(range);
        }
        // Hyphen in the middle, like "1-10".
        else
        {
            const StrRange numberRange1 = { currRange.beg, hyphenPos };
            const StrRange numberRange2 = { hyphenPos + 1, currRange.end };
            RangeType range;
            if(!StrRangeToUint(numberRange1, range.first) ||
                !StrRangeToUint(numberRange2, range.second) ||
                range.second < range.first)
            {
                return false;
            }
            m_Ranges.push_back(range);
        }

        // Skip ','
        currRange.beg = currRange.end + 1;
    }

    return true;
}

template<typename T>
bool RangeSequence<T>::Includes(T number) const
{
    for(const auto& it : m_Ranges)
    {
        if(number >= it.first && number <= it.second)
        {
            return true;
        }
    }
    return false;
}

/*
class RandomNumberGenerator
{
public:
    RandomNumberGenerator() : m_Value{GetTickCount()} {}
    RandomNumberGenerator(uint32_t seed) : m_Value{seed} { }
    void Seed(uint32_t seed) { m_Value = seed; }
    uint32_t Generate() { return GenerateFast() ^ (GenerateFast() >> 7); }

private:
    uint32_t m_Value;
    uint32_t GenerateFast() { return m_Value = (m_Value * 196314165 + 907633515); }
};

enum class CONSOLE_COLOR
{
    INFO,
    NORMAL,
    WARNING,
    ERROR_,
    COUNT
};

void SetConsoleColor(CONSOLE_COLOR color);

void PrintMessage(CONSOLE_COLOR color, const char* msg);
void PrintMessage(CONSOLE_COLOR color, const wchar_t* msg);

inline void Print(const char* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); }
inline void Print(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::NORMAL, msg); }
inline void PrintWarning(const char* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); }
inline void PrintWarning(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::WARNING, msg); }
inline void PrintError(const char* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); }
inline void PrintError(const wchar_t* msg) { PrintMessage(CONSOLE_COLOR::ERROR_, msg); }

void PrintMessageV(CONSOLE_COLOR color, const char* format, va_list argList);
void PrintMessageV(CONSOLE_COLOR color, const wchar_t* format, va_list argList);
void PrintMessageF(CONSOLE_COLOR color, const char* format, ...);
void PrintMessageF(CONSOLE_COLOR color, const wchar_t* format, ...);
void PrintWarningF(const char* format, ...);
void PrintWarningF(const wchar_t* format, ...);
void PrintErrorF(const char* format, ...);
void PrintErrorF(const wchar_t* format, ...);
*/
