/**
 * @license    BSD 3-Clause
 * @copyright  Pawel Okas
 * @version    $Id$
 * @brief
 *
 * @authors    Pawel Okas
 * created on: 30-03-2019
 *
 * @copyright Copyright (c) 2019, Pawel Okas
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 *     1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *     2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
 *        documentation and/or other materials provided with the distribution.
 *     3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
 *        software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef _AT24MAC_H_
#define _AT24MAC_H_

#include <charconv>
#include <gsl/span>
#include <optional>
#include <string_view>
#include "I2CDevice/I2CDevice.h"
#include "microhal.h"

/**
 * \addtogroup Devices
 * @{
 * @class AT24MAC
 * @}
 */
class AT24MAC {
 public:
    using I2C = microhal::I2C;
    enum class Error {
        AcknowledgeFailure = static_cast<int>(I2C::Error::AcknowledgeFailure),
        ArbitrationLost = static_cast<int>(I2C::Error::ArbitrationLost),
        Bus = static_cast<int>(I2C::Error::Bus),
        None = static_cast<int>(I2C::Error::None),
        Overrun = static_cast<int>(I2C::Error::Overrun),
        Timeout = static_cast<int>(I2C::Error::Timeout),
        Unknown = static_cast<int>(I2C::Error::Unknown),
        DataOverflow,
        Addres
    };

    struct SerialNumber {
        uint8_t serial[128 / 8];

        bool operator!=(SerialNumber b) { return std::equal(std::begin(serial), std::begin(serial) + sizeof(serial), std::begin(b.serial)); }
        std::string toString() const {
            std::string str;
            str.reserve(18);
            for (uint_fast8_t i = 0; i < sizeof(serial); i++) {
                std::array<char, 2> buff;
                auto [p, ec] = std::to_chars(buff.data(), buff.data() + buff.size(), serial[i], 16);
                str.append(std::string_view(buff.data(), p - str.data()));
                if (i + 1 != sizeof(serial)) {
                    str.append(":");
                }
            }
            return str;
        }
    };

    static constexpr const size_t pageSize = 16;
    static constexpr const size_t memorySizeInBytes = 256;

 private:
    using Endianness = microhal::Endianness;
    using Access = microhal::Access;
    using span = gsl::span<uint8_t>;  // Todo change to std::span when it will be available in gcc

    // create alias to microhal::Address, we just want to type less
    template <typename T, T i>
    using Address = microhal::Address<T, i>;

    struct Register {
        static constexpr auto SerialNumberReg =
            microhal::makeRegister<SerialNumber, Access::ReadOnly, Endianness::Little>(Address<uint8_t, 0b1000'0000>{});
        static constexpr auto EUIAddress = microhal::makeRegister<uint64_t, Access::ReadOnly, Endianness::Big>(Address<uint8_t, 0b1001'1000>{});
    };

    static constexpr uint8_t getMACi2cAddrFromMemoryI2cAddr(uint8_t memoryAddress) { return (memoryAddress & 0x0F) | 0xB0; }

 public:
    AT24MAC(I2C &i2c, uint8_t address) : memory(i2c, address), mac(i2c, getMACi2cAddrFromMemoryI2cAddr(address)) {}

    static std::string_view toString(Error error);

    Error readEUI(uint64_t &eui) { return static_cast<Error>(mac.readRegister(Register::EUIAddress, eui)); }
    Error readSerialNumber(SerialNumber &serial) { return static_cast<Error>(mac.readRegister(Register::SerialNumberReg, serial)); }

    // Memory access functions
    Error readByte(uint8_t address, uint8_t &data) { return static_cast<Error>(memory.read(address, data)); }
    Error read(uint8_t address, span data) { return static_cast<Error>(memory.read(address, data)); }

    Error writeByte(uint8_t address, uint8_t data) { return static_cast<Error>(memory.write(address, data)); }
    Error writePage(uint8_t pageAddress, span data) {
        if (data.size_bytes() > 16) return Error::DataOverflow;
        if ((pageAddress % 16) != 0) return Error::Addres;
        return static_cast<Error>(memory.write(pageAddress, data));
    }
    Error write(uint8_t address, span data) {
        if (data.size_bytes() > memorySizeInBytes) return Error::DataOverflow;

        uint_fast8_t bytesInFirstPage = pageSize - (address % pageSize);
        span firstPage(data.data(), bytesInFirstPage);
        writePage(address, firstPage);

        address += bytesInFirstPage;
        uint_fast8_t bytesToWrite = (data.size_bytes() - bytesInFirstPage);
        uint8_t *dataPtr = data.data() + bytesInFirstPage;
        while (bytesToWrite / pageSize) {
            span page(dataPtr, pageSize);
            writePage(address, page);
            address += pageSize;
            dataPtr += pageSize;
            bytesToWrite -= pageSize;
        }

        uint_fast8_t lastPageSize = bytesToWrite % pageSize;
        if (lastPageSize) {
            span page(dataPtr, lastPageSize);
            writePage(address, page);
        }
        return Error::None;
    }

    void writeWait() { std::this_thread::sleep_for(std::chrono::milliseconds{5}); }

 private:
    microhal::I2CDevice memory;
    microhal::I2CDevice mac;
};

#endif /* _AT24MAC_H_ */


#ifndef VKST_WSI_WINDOW_H
#define VKST_WSI_WINDOW_H

#include <turf/c/core.h>
#include <gsl.h>
#include <wsi/input.h>
#include <functional>

namespace wsi {

enum class window_options : uint8_t {
  none = 0,
  decorated = (1 << 1),
  sizeable = (1 << 2),
  fullscreen_windowed = (1 << 3),
}; // enum class window_options

namespace impl {

template <class D>
class window {
public:
  void retitle(gsl::czstring title) noexcept {
    static_cast<D*>(this)->do_retitle(title);
  }
  std::string title() const noexcept {
    return static_cast<D const*>(this)->do_title();
  }

  rect2d const& topleft_size() const noexcept { return _topleft_size; }

  extent2d const& size() const noexcept { return _topleft_size.extent; }
  void resize(extent2d const& size) noexcept {
    static_cast<D*>(this)->do_resize(size);
  }

  offset2d const& position() const noexcept { return _topleft_size.offset; }
  void reposition(offset2d const& position) noexcept {
    static_cast<D*>(this)->do_reposition(position);
  }

  void show() noexcept { static_cast<D*>(this)->do_show(); }
  void hide() noexcept { static_cast<D*>(this)->do_hide(); }

  void close() noexcept { static_cast<D*>(this)->do_close(); }
  bool closed() const noexcept { return _closed; }

  keyset const& keys() const noexcept { return _keys; }
  buttonset const& buttons() const noexcept { return _buttons; }
  int scroll() const noexcept { return _scroll; }

  offset2d cursor_pos() const noexcept {
    return static_cast<D const*>(this)->do_cursor_pos();
  }

  void poll_events() noexcept { static_cast<D*>(this)->do_poll_events(); }

  using resize_delegate = std::function<void(D*, extent2d const&)>;
  void on_resize(resize_delegate delegate) noexcept {
    _on_resize = std::move(delegate);
  }

  using reposition_delegate = std::function<void(D*, offset2d const&)>;
  void on_reposition(reposition_delegate delegate) noexcept {
    _on_reposition = std::move(delegate);
  }

  using close_delegate = std::function<void(D*)>;
  void on_close(close_delegate delegate) noexcept {
    _on_close = std::move(delegate);
  }

  constexpr window() noexcept {}
  constexpr window(rect2d topleft_size) noexcept
  : _topleft_size{std::move(topleft_size)} {}

protected:
  rect2d _topleft_size{};
  bool _closed{false};
  keyset _keys{};
  buttonset _buttons{};
  int _scroll{0};

  resize_delegate _on_resize{[](auto, auto) {}};
  reposition_delegate _on_reposition{[](auto, auto) {}};
  close_delegate _on_close{[](D*) {}};
}; // class window

} // namespace impl

inline constexpr auto operator|(window_options a, window_options b) noexcept {
  using U = std::underlying_type_t<window_options>;
  return static_cast<window_options>(static_cast<U>(a) | static_cast<U>(b));
}

inline constexpr auto operator&(window_options a, window_options b) noexcept {
  using U = std::underlying_type_t<window_options>;
  return static_cast<window_options>(static_cast<U>(a) & static_cast<U>(b));
}

} // namespace wsi

// clang-format off
#if TURF_TARGET_WIN32
#  include <wsi/window_win32.h>
#elif TURF_KERNEL_LINUX
#  include <wsi/window_xlib.h>
#else
#  error "Unsupported platform"
#endif
// clang-format on

#endif // VKST_WSI_WINDOW_H


#pragma once

#include <nextalign/nextalign.h>

#include <gsl/string_span>

template<typename Letter>
using SequenceSpan = gsl::basic_string_span<Letter, gsl::dynamic_extent>;

template<typename Letter>
struct InsertionInternal {
  int begin;
  int end;
  Sequence<Letter> seq;
};

struct PeptideInternal {
  std::string name;
  AminoacidSequence seq;
  std::vector<InsertionInternal<Aminoacid>> insertions;
};


#ifndef rpsr_f7f329f9_ae61_4fcf_b0c9_8a96216ed0e0_h
#define rpsr_f7f329f9_ae61_4fcf_b0c9_8a96216ed0e0_h

#include <rathen\config.h>

/*
 * This version of API wrappers were used by the flex & bison due to the cpp generator was still unstable.
 * Any kind of cpp style announcement should not appear in the header file.
 */

#ifndef __cplusplus
#ifdef _UNICODE
typedef wchar_t rchar;
#else
typedef char rchar;
#endif
#else
#include <gslib\string.h>
typedef gs::gchar rchar;
#endif

#ifndef __cplusplus
typedef void rpswrap;
typedef void rpsobj;
typedef void rpsparser;
#else
#include <rathen\parser.h>
typedef gs::rathen::parser::wrapper rpswrap;
typedef gs::rathen::ps_obj rpsobj;
typedef gs::rathen::parser rpsparser;
#endif

/* file: parser.h, line: 14 */
enum rpobj_tag
{
    rpt_root,
    rpt_block,
    rpt_value,
    rpt_expression,
    rpt_operator,
    rpt_function,
    rpt_calling,
    rpt_if_statement,
    rpt_loop_statement,
    rpt_go_statement,
    rpt_stop_statement,
    rpt_exit_statement,
    rpt_variable,
    rpt_constant,
};

enum rpv_tag
{
    rvt_string,
    rvt_integer,
    rvt_float,
};

enum rps_encoding
{
    rpe_ascii,
    rpe_utf8,
    rpe_utf16,
    // ...
};

typedef struct
{
    rps_encoding    encoding;
    const char*     file;
    char*           source;
    char*           current;
    int             length;
    int             line;
    int             position;
}
rgcontext;

typedef struct
{
    rps_encoding    encoding;
    const rchar*    file;
    char*           source;
    char*           current;
    int             length;
    int             line;
    int             position;
    const rchar*    keyword;
    rpsparser*      parser;
    rpswrap*        wrapper;
}
rpscontext;

#ifdef __cplusplus
extern "C" {
#endif

extern void rathen_yy_setup(rgcontext* context, const char* file, rps_encoding encoding = rpe_utf8);
extern void rathen_yy_ssetup(rgcontext* context, const char* file, char* source, int length, rps_encoding encoding = rpe_utf8);
extern void rathen_yy_release(rgcontext* context);
extern rpscontext* rathen_yy_select(rpscontext* context);
extern void rathen_yy_parse(rpsparser* ps, const char* file);
extern void rathen_yy_sparse(rpsparser* ps, const char* file, char* source, int length);
extern int rathen_yy_read(char* buff, int* bytes, int cap);

extern bool rathen_is_type(const rchar* str, int len = -1);
extern bool rathen_is_keyword(const rchar* str, int len = -1);
extern rpsparser* rathen_create_parser(const rchar* name);
extern void rathen_destroy_parser(rpsparser* rps);
extern rpswrap* rathen_get_root(rpsparser* rps);
extern rpswrap* rathen_create_value(rpscontext* context, const rchar* name, const rchar* tyname, bool ref);
extern rpswrap* rathen_create_function(rpscontext* context, const rchar* name, const rchar* retyname, bool retref);
extern rpswrap* rathen_create_constant(rpscontext* context, const rchar* name, rpv_tag tag, const rchar* vstr);
//extern rpswrap* rathen_create();

#ifdef __cplusplus
};
#endif

#endif


//
//  View.hpp
//  PretendToWork
//
//  Created by tqtifnypmb on 14/12/2017.
//  Copyright © 2017 tqtifnypmb. All rights reserved.
//

#pragma once

#include "../rope/Range.h"
#include "../types.h"
#include "../editor/Editor.h"

#include <memory>
#include <utility>
#include <gsl/gsl>
#include <map>
#include <string>

namespace brick
{

class View {
public:

    using UpdateCb = std::function<void(View* view, const Editor::DeltaList&)>;

    View(size_t viewId, UpdateCb cb);
    View(size_t viewId, View* parent, UpdateCb cb);
    View(size_t viewId, const std::string& filePath, UpdateCb cb);

    ~View();

    void scroll(size_t begRow, size_t endRow);

    template<class Converter>
    void insert(gsl::span<const char> bytes);
    void insert(const detail::CodePointList& cplist);

    void erase();
    void undo();
    void select(Range sel);
    Range selection() const {
        return sel_;
    }

    void save();
    void save(const std::string& filePath);

    template <class Converter>
    std::map<size_t, std::string> region() {
        return region<Converter>(visibleRange_.first, visibleRange_.second);
    }

    template <class Converter>
    std::map<size_t, std::string> region(size_t begRow, size_t endRow);

    size_t viewId() const {
        return viewId_;
    }

    size_t viewSize() const {
        return viewSize_;
    }

    bool hasChildren() const {
        return !children_.empty();
    }

    void removeChild(const View* child) {
        auto found = std::find_if(children_.begin(), children_.end(), [child](const auto& v) { return v->viewId_ == child->viewId_; });
        if (found != children_.end()) {
            children_.erase(found);
        }
    }

    const std::vector<View*>& children() const {
        return children_;
    }

    const std::string filePath() const {
        return filePath_;
    }

    bool hasParent() const {
        return parent() != nullptr;
    }

    const View* parent() const {
        return parent_;
    }

private:
    void editor_sync_cb(const Editor::DeltaList& deltaList);
    void update(std::vector<View*>& src);

    std::map<size_t, detail::CodePointList> regionImpl(size_t begRow, size_t endRow);
    std::map<size_t, detail::CodePointList> regionImpl() {
        return regionImpl(visibleRange_.first, visibleRange_.second);
    }

    UpdateCb update_cb_;

    std::pair<size_t, size_t> visibleRange_;
    size_t viewId_;
    size_t viewSize_;
    View* parent_;
    Range sel_;
    std::vector<View*> children_;
    std::unique_ptr<Editor> editor_;
    std::string filePath_;
};

template<class Converter>
void View::insert(gsl::span<const char> bytes) {
    insert(Converter::encode(bytes));
}

template <class Converter>
std::map<size_t, std::string> View::region(size_t begRow, size_t endRow) {
    auto lines = regionImpl(begRow, endRow);
    std::map<size_t, std::string> ret;
    for (const auto& line : lines) {
        ret[line.first] = Converter::decode(line.second);
    }
    return ret;
}

}


#ifndef __TEST_UTILS_H__
#define __TEST_UTILS_H__

#define THEN_WHEN(x)
#define THEN_CHECK(x)

#include <initializer_list>
#include <memory>
#include <string>
#include <vector>

#include <gsl/gsl>
#include <gsl/string_span>

#include "base-utils/yaml.h"
#include "config/path.h"
#include "config/pattern.h"
#include "plugins/pluginUtils.h"

#include "executorStub.h"

namespace execHelper {
namespace config {
class SettingsNode;
} // namespace config

namespace test {
namespace baseUtils {
class ConfigFileWriter;
} // namespace baseUtils
} // namespace test
} // namespace execHelper

namespace std {
template <typename T>
std::ostream& operator<<(std::ostream& os, const std::vector<T>& stream) {
    for(const auto& element : stream) {
        os << element << ", ";
    }
    return os;
}

template <typename T, typename U>
std::ostream& operator<<(std::ostream& os, const std::pair<T, U>& stream) {
    os << stream.first << ": " << stream.second;
    return os;
}
} // namespace std

namespace execHelper {
namespace test {
namespace utils {
using Patterns = std::vector<config::Pattern>;

using Arguments = std::vector<std::string>;
struct MainVariables {
    int argc;
    std::unique_ptr<char*[]> argv;

    explicit MainVariables(const Arguments& arguments);
};

template <typename T> void appendVectors(T& appendTo, const T& appendFrom) {
    appendTo.insert(std::end(appendTo), std::begin(appendFrom),
                    std::end(appendFrom));
}

baseUtils::YamlWriter toYaml(const config::SettingsNode& settings,
                             const config::Patterns& patterns) noexcept;
void writeSettingsFile(
    gsl::not_null<baseUtils::ConfigFileWriter*> configFileWriter,
    const config::SettingsNode& settings,
    const config::Patterns& patterns) noexcept;
std::string convertToConfig(const Patterns& patterns) noexcept;
std::string
convertToConfig(const config::SettingsNode& rootSettings,
                const std::string& prepend = std::string()) noexcept;
std::string
convertToConfig(const config::SettingsNode& settings, const Patterns& patterns,
                const std::string& prepend = std::string()) noexcept;

std::string convertToConfig(std::string key, std::string value,
                            const std::string& prepend = std::string());
std::string convertToConfig(const std::string& key,
                            const std::initializer_list<std::string>& values,
                            const std::string& prepend = std::string());
std::string convertToConfig(const std::string& key,
                            const std::vector<std::string>& values,
                            const std::string& prepend = std::string());

std::string
convertToConfig(const std::initializer_list<std::string>& keys,
                const std::string& value,
                const std::string& prepend = std::string()) noexcept;
std::string
convertToConfig(const std::initializer_list<std::string>& keys,
                const std::initializer_list<std::string>& values,
                const std::string& prepend = std::string()) noexcept;
std::string
convertToConfig(const std::initializer_list<std::string>& keys,
                const std::vector<std::string>& values,
                const std::string& prepend = std::string()) noexcept;
std::string
convertToConfig(const std::vector<std::string>& keys,
                const std::initializer_list<std::string>& values,
                const std::string& prepend = std::string()) noexcept;
std::string
convertToConfig(const std::vector<std::string>& keys,
                const std::vector<std::string>& values,
                const std::string& prepend = std::string()) noexcept;
std::string basename(const std::string& file);

config::PatternCombinations createPatternCombination(
    const std::initializer_list<config::PatternKey>& keys,
    const std::initializer_list<config::PatternValue>& values) noexcept;

config::PatternCombinations
createPatternCombination(const config::PatternKeys& keys,
                         const config::PatternValues& values) noexcept;
plugins::PatternPermutator
makePatternPermutator(const config::Patterns& patterns) noexcept;
core::test::ExecutorStub::TaskQueue
getExpectedTasks(const core::Task& task,
                 const config::Patterns patterns) noexcept;
core::test::ExecutorStub::TaskQueue
getExpectedTasks(const core::test::ExecutorStub::TaskQueue& tasks,
                 const config::Patterns patterns) noexcept;

std::string toString(const config::SettingsNode& settings,
                     unsigned int nbOfTabs = 0) noexcept;

std::string inheritWorkingDirKey() noexcept;
Patterns getPredefinedPatterns() noexcept;
} // namespace utils
} // namespace test
} // namespace execHelper

#endif /* __TEST_UTILS_H__ */


/* interpolation/accel.c
 *
 * Copyright (C) 1996, 1997, 1998, 1999, 2000 Gerard Jungman
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/* Author:  G. Jungman
 */
#include <config.h>
#include <stdlib.h>
#include <gsl/gsl_errno.h>
#include <gsl/gsl_interp.h>

gsl_interp_accel *
gsl_interp_accel_alloc (void)
{
  gsl_interp_accel *a = (gsl_interp_accel *) malloc (sizeof (gsl_interp_accel));
  if (a == 0)
    {
      GSL_ERROR_NULL("could not allocate space for gsl_interp_accel", GSL_ENOMEM);
    }

  a->cache = 0;
  a->hit_count = 0;
  a->miss_count = 0;

  return a;
}

int
gsl_interp_accel_reset (gsl_interp_accel * a)
{
  a->cache = 0;
  a->hit_count = 0;
  a->miss_count = 0;

  return GSL_SUCCESS;
}

void
gsl_interp_accel_free (gsl_interp_accel * a)
{
  RETURN_IF_NULL (a);
  free (a);
}


#pragma once

#include <memory>
#include "halley/text/halleystring.h"
#include "halley/core/graphics/texture.h"
#include <gsl/gsl>

namespace Halley
{
	enum class ShaderType;
	class MaterialDataBlock;
	class Material;
	enum class ShaderParameterType;
	class Painter;
	class MaterialDefinition;
	class MaterialParameter;
	class MaterialTextureParameter;
	class VideoAPI;

	class MaterialConstantBuffer
	{
	public:
		virtual ~MaterialConstantBuffer() {}

		virtual void update(const MaterialDataBlock& dataBlock) = 0;
	};

	enum class MaterialDataBlockType
	{
		// Shared blocks are not stored locally in the material (e.g. the HalleyBlock, stored by the engine)
		SharedLocal,    // Shared, this keeps the canonical copy
		SharedExternal, // Shared, only a reference
		Local           // Local data
	};

	class MaterialDataBlock
	{
		friend class Material;

	public:
		MaterialDataBlock();
		MaterialDataBlock(MaterialDataBlockType type, size_t size, int bindPoint, const String& name, const MaterialDefinition& def);
		MaterialDataBlock(const MaterialDataBlock& other);
		MaterialDataBlock(MaterialDataBlock&& other) noexcept;

		MaterialConstantBuffer& getConstantBuffer() const;
		int getAddress(int pass, ShaderType stage) const;
		int getBindPoint() const;
		gsl::span<const gsl::byte> getData() const;
		MaterialDataBlockType getType() const;

	private:
		std::unique_ptr<MaterialConstantBuffer> constantBuffer;
		Bytes data;
		Vector<int> addresses;
		MaterialDataBlockType dataBlockType;
		int bindPoint = 0;
		bool dirty = true;

		bool setUniform(size_t offset, ShaderParameterType type, const void* data);
		void upload(VideoAPI* api);
	};

	class Material
	{
		friend class MaterialParameter;

	public:
		Material(const Material& other);
		explicit Material(std::shared_ptr<const MaterialDefinition> materialDefinition, bool forceLocalBlocks = false); // forceLocalBlocks is for engine use only

		void bind(int pass, Painter& painter);
		void uploadData(Painter& painter);
		static void resetBindCache();

		bool operator==(const Material& material) const;
		bool operator!=(const Material& material) const;

		const MaterialDefinition& getDefinition() const { return *materialDefinition; }

		std::shared_ptr<Material> clone() const;

		const std::shared_ptr<const Texture>& getTexture(int textureUnit) const;
		const Vector<MaterialTextureParameter>& getTextureUniforms() const;
		const std::vector<std::shared_ptr<const Texture>>& getTextures() const;

		const Vector<MaterialParameter>& getUniforms() const;
		const Vector<MaterialDataBlock>& getDataBlocks() const;

		void setPassEnabled(int pass, bool enabled);
		bool isPassEnabled(int pass) const;

		Material& set(const String& name, const std::shared_ptr<const Texture>& texture);
		Material& set(const String& name, const std::shared_ptr<Texture>& texture);

		bool hasParameter(const String& name) const;

		template <typename T>
		Material& set(const String& name, const T& value)
		{
			getParameter(name) = value;
			return *this;
		}

		uint64_t getHash() const;

	private:
		std::shared_ptr<const MaterialDefinition> materialDefinition;

		Vector<MaterialParameter> uniforms;
		Vector<MaterialTextureParameter> textureUniforms;
		Vector<MaterialDataBlock> dataBlocks;
		std::vector<std::shared_ptr<const Texture>> textures;

		std::vector<char> passEnabled;

		mutable uint64_t hashValue = 0;
		mutable bool needToUpdateHash = true;
		bool needToUploadData = true;

		void initUniforms(bool forceLocalBlocks);
		MaterialParameter& getParameter(const String& name);

		void setUniform(int blockNumber, size_t offset, ShaderParameterType type, const void* data);
		uint64_t computeHash() const;

		const std::shared_ptr<const Texture>& getFallbackTexture() const;
	};
}


/*
 * Copyright (c) 2016-2021 lymastee, All rights reserved.
 * Contact: lymastee@hotmail.com
 *
 * This file is part of the gslib project.
 *
 * 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

#ifndef style_ac50d5be_0f99_48dc_908f_ab8a309b2a27_h
#define style_ac50d5be_0f99_48dc_908f_ab8a309b2a27_h

#include <gslib/string.h>
#include <gslib/std.h>
#include <ariel/type.h>
#include <ariel/painter.h>

__ariel_begin__

enum style_sheet_type
{
    sst_unknown     = -1,
    sst_color       = 0,
    sst_integer,
    sst_boolean,
    sst_float,
    sst_string,
    sst_capacity,
};

extern const string& get_style_sheet_type_name(style_sheet_type sst);
typedef std::pair<style_sheet_type, string> style_sheet_def;
typedef unordered_map<string, int> style_sheet_info_map;

struct accel_key;

class __gs_novtable style_sheet abstract
{
public:
    static const int npos = -1;

public:
    style_sheet(const style_sheet_def* ssp, int len);
    virtual ~style_sheet() {}
    virtual bool get_value(const string& name, string& value) = 0;
    virtual void set_value(const string& name, const string& value) = 0;
    virtual int get_content_size() const { return _ss_length; }
    virtual style_sheet_type get_content_type(int index) const;
    virtual const string& get_content_name(int index) const;
    virtual void flush_style() = 0;

protected:
    const style_sheet_def*  _ss_pairs;
    int                     _ss_length;
    style_sheet_info_map    _ss_info;

public:
    void initialize_style_sheet(const style_sheet_def* ssp, int len);
    int get_style_sheet_index(const string& name) const;

protected:
    static bool from_color(string& str, const color& cr);
    static bool to_color(color& cr, const string& str);
    static bool from_integer(string& str, int i);
    static bool to_integer(int& i, const string& str);
    static bool from_boolean(string& str, bool b);
    static bool to_boolean(bool& b, const string& str);
    static bool from_float(string& str, float f);
    static bool to_float(float& f, const string& str);
    static bool from_accel_key(string& str, const accel_key& k);
    static bool to_accel_key(accel_key& k, const string& str);
    static void setup_brush_by_color(painter_brush& brush, const color& cr);
    static void setup_pen_by_color(painter_pen& pen, const color& cr);
    static void setup_font(font& ft, const string& name, int size);
};

__ariel_end__

#endif


#pragma once

#include <cstring>
#include <string>
#include <sstream>
#include <iomanip>
#include <array>
#include <string_view>
#include <iterator>
#include <random>
#include <memory>
#include <functional>
#include <type_traits>
#include <assert.h>
#include <gsl/span>

#ifdef _WIN32
#include <objbase.h>
#elif defined(__linux__) || defined(__unix__)
#include <uuid/uuid.h>
#elif defined(__APPLE__)
#include <CoreFoundation/CFUUID.h>
#endif

namespace uuids
{
   namespace detail
   {
      template <typename TChar>
      constexpr inline unsigned char hex2char(TChar const ch)
      {
         if (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9'))
            return static_cast<unsigned char>(ch - static_cast<TChar>('0'));
         if (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f'))
            return static_cast<unsigned char>(10 + ch - static_cast<TChar>('a'));
         if (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F'))
            return static_cast<unsigned char>(10 + ch - static_cast<TChar>('A'));
         return 0;
      }

      template <typename TChar>
      constexpr inline bool is_hex(TChar const ch)
      {
         return
            (ch >= static_cast<TChar>('0') && ch <= static_cast<TChar>('9')) ||
            (ch >= static_cast<TChar>('a') && ch <= static_cast<TChar>('f')) ||
            (ch >= static_cast<TChar>('A') && ch <= static_cast<TChar>('F'));
      }

      template <typename TChar>
      constexpr inline unsigned char hexpair2char(TChar const a, TChar const b)
      {
         return static_cast<unsigned char>((hex2char(a) << 4) | hex2char(b));
      }

      class sha1
      {
      public:
         using digest32_t = uint32_t[5];
         using digest8_t = uint8_t[20];

         static constexpr unsigned int block_bytes = 64;

         inline static uint32_t left_rotate(uint32_t value, size_t const count)
         {
            return (value << count) ^ (value >> (32 - count));
         }

         sha1() { reset(); }

         void reset()
         {
            m_digest[0] = 0x67452301;
            m_digest[1] = 0xEFCDAB89;
            m_digest[2] = 0x98BADCFE;
            m_digest[3] = 0x10325476;
            m_digest[4] = 0xC3D2E1F0;
            m_blockByteIndex = 0;
            m_byteCount = 0;
         }

         void process_byte(uint8_t octet)
         {
            this->m_block[this->m_blockByteIndex++] = octet;
            ++this->m_byteCount;
            if (m_blockByteIndex == block_bytes)
            {
               this->m_blockByteIndex = 0;
               process_block();
            }
         }

         void process_block(void const * const start, void const * const end)
         {
            const uint8_t* begin = static_cast<const uint8_t*>(start);
            const uint8_t* finish = static_cast<const uint8_t*>(end);
            while (begin != finish)
            {
               process_byte(*begin);
               begin++;
            }
         }

         void process_bytes(void const * const data, size_t const len)
         {
            const uint8_t* block = static_cast<const uint8_t*>(data);
            process_block(block, block + len);
         }

         uint32_t const * get_digest(digest32_t digest)
         {
            size_t const bitCount = this->m_byteCount * 8;
            process_byte(0x80);
            if (this->m_blockByteIndex > 56) {
               while (m_blockByteIndex != 0) {
                  process_byte(0);
               }
               while (m_blockByteIndex < 56) {
                  process_byte(0);
               }
            }
            else {
               while (m_blockByteIndex < 56) {
                  process_byte(0);
               }
            }
            process_byte(0);
            process_byte(0);
            process_byte(0);
            process_byte(0);
            process_byte(static_cast<unsigned char>((bitCount >> 24) & 0xFF));
            process_byte(static_cast<unsigned char>((bitCount >> 16) & 0xFF));
            process_byte(static_cast<unsigned char>((bitCount >> 8) & 0xFF));
            process_byte(static_cast<unsigned char>((bitCount) & 0xFF));

            memcpy(digest, m_digest, 5 * sizeof(uint32_t));
            return digest;
         }

         uint8_t const * get_digest_bytes(digest8_t digest)
         {
            digest32_t d32;
            get_digest(d32);
            size_t di = 0;
            digest[di++] = (uint8_t)((d32[0] >> 24) & 0xFF);
            digest[di++] = (uint8_t)((d32[0] >> 16) & 0xFF);
            digest[di++] = (uint8_t)((d32[0] >> 8) & 0xFF);
            digest[di++] = (uint8_t)((d32[0]) & 0xFF);

            digest[di++] = (uint8_t)((d32[1] >> 24) & 0xFF);
            digest[di++] = (uint8_t)((d32[1] >> 16) & 0xFF);
            digest[di++] = (uint8_t)((d32[1] >> 8) & 0xFF);
            digest[di++] = (uint8_t)((d32[1]) & 0xFF);

            digest[di++] = (uint8_t)((d32[2] >> 24) & 0xFF);
            digest[di++] = (uint8_t)((d32[2] >> 16) & 0xFF);
            digest[di++] = (uint8_t)((d32[2] >> 8) & 0xFF);
            digest[di++] = (uint8_t)((d32[2]) & 0xFF);

            digest[di++] = (uint8_t)((d32[3] >> 24) & 0xFF);
            digest[di++] = (uint8_t)((d32[3] >> 16) & 0xFF);
            digest[di++] = (uint8_t)((d32[3] >> 8) & 0xFF);
            digest[di++] = ((d32[3]) & 0xFF);

            digest[di++] = (uint8_t)((d32[4] >> 24) & 0xFF);
            digest[di++] = (uint8_t)((d32[4] >> 16) & 0xFF);
            digest[di++] = (uint8_t)((d32[4] >> 8) & 0xFF);
            digest[di++] = (uint8_t)((d32[4]) & 0xFF);

            return digest;
         }

      private:
         void process_block()
         {
            uint32_t w[80];
            for (size_t i = 0; i < 16; i++) {
               w[i] = (m_block[i * 4 + 0] << 24);
               w[i] |= (m_block[i * 4 + 1] << 16);
               w[i] |= (m_block[i * 4 + 2] << 8);
               w[i] |= (m_block[i * 4 + 3]);
            }
            for (size_t i = 16; i < 80; i++) {
               w[i] = left_rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1);
            }

            uint32_t a = m_digest[0];
            uint32_t b = m_digest[1];
            uint32_t c = m_digest[2];
            uint32_t d = m_digest[3];
            uint32_t e = m_digest[4];

            for (std::size_t i = 0; i < 80; ++i)
            {
               uint32_t f = 0;
               uint32_t k = 0;

               if (i < 20) {
                  f = (b & c) | (~b & d);
                  k = 0x5A827999;
               }
               else if (i < 40) {
                  f = b ^ c ^ d;
                  k = 0x6ED9EBA1;
               }
               else if (i < 60) {
                  f = (b & c) | (b & d) | (c & d);
                  k = 0x8F1BBCDC;
               }
               else {
                  f = b ^ c ^ d;
                  k = 0xCA62C1D6;
               }
               uint32_t temp = left_rotate(a, 5) + f + e + k + w[i];
               e = d;
               d = c;
               c = left_rotate(b, 30);
               b = a;
               a = temp;
            }

            m_digest[0] += a;
            m_digest[1] += b;
            m_digest[2] += c;
            m_digest[3] += d;
            m_digest[4] += e;
         }

      private:
         digest32_t m_digest;
         uint8_t m_block[64];
         size_t m_blockByteIndex;
         size_t m_byteCount;
      };
   }

   // UUID format https://tools.ietf.org/html/rfc4122
   // Field	                     NDR Data Type	   Octet #	Note
   // --------------------------------------------------------------------------------------------------------------------------
   // time_low	                  unsigned long	   0 - 3	   The low field of the timestamp.
   // time_mid	                  unsigned short	   4 - 5	   The middle field of the timestamp.
   // time_hi_and_version	      unsigned short	   6 - 7	   The high field of the timestamp multiplexed with the version number.
   // clock_seq_hi_and_reserved	unsigned small	   8	      The high field of the clock sequence multiplexed with the variant.
   // clock_seq_low	            unsigned small	   9	      The low field of the clock sequence.
   // node	                     character	      10 - 15	The spatially unique node identifier.
   // --------------------------------------------------------------------------------------------------------------------------
   // 0                   1                   2                   3
   //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   // |                          time_low                             |
   // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   // |       time_mid                |         time_hi_and_version   |
   // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   // |clk_seq_hi_res |  clk_seq_low  |         node (0-1)            |
   // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   // |                         node (2-5)                            |
   // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

   // indicated by a bit pattern in octet 8, marked with N in xxxxxxxx-xxxx-xxxx-Nxxx-xxxxxxxxxxxx
   enum class uuid_variant
   {
      // NCS backward compatibility (with the obsolete Apollo Network Computing System 1.5 UUID format)
      // N bit pattern: 0xxx
      // > the first 6 octets of the UUID are a 48-bit timestamp (the number of 4 microsecond units of time since 1 Jan 1980 UTC);
      // > the next 2 octets are reserved;
      // > the next octet is the "address family";
      // > the final 7 octets are a 56-bit host ID in the form specified by the address family
      ncs,

      // RFC 4122/DCE 1.1
      // N bit pattern: 10xx
      // > big-endian byte order
      rfc,

      // Microsoft Corporation backward compatibility
      // N bit pattern: 110x
      // > little endian byte order
      // > formely used in the Component Object Model (COM) library
      microsoft,

      // reserved for possible future definition
      // N bit pattern: 111x
      reserved
   };

   struct uuid_error : public std::runtime_error
   {
      explicit uuid_error(std::string_view message)
         : std::runtime_error(message.data())
      {
      }

      explicit uuid_error(char const * message)
         : std::runtime_error(message)
      {
      }
   };

   // indicated by a bit pattern in octet 6, marked with M in xxxxxxxx-xxxx-Mxxx-xxxx-xxxxxxxxxxxx
   enum class uuid_version
   {
      none = 0, // only possible for nil or invalid uuids
      time_based = 1,  // The time-based version specified in RFC 4122
      dce_security = 2,  // DCE Security version, with embedded POSIX UIDs.
      name_based_md5 = 3,  // The name-based version specified in RFS 4122 with MD5 hashing
      random_number_based = 4,  // The randomly or pseudo-randomly generated version specified in RFS 4122
      name_based_sha1 = 5   // The name-based version specified in RFS 4122 with SHA1 hashing
   };

   struct uuid
   {
      struct uuid_const_iterator
      {
         using self_type         = uuid_const_iterator;
         using value_type        = uint8_t;
         using reference         = uint8_t const &;
         using pointer           = uint8_t const *;
         using iterator_category = std::random_access_iterator_tag;
         using difference_type   = ptrdiff_t;

      protected:
         pointer ptr = nullptr;
         size_t  index = 0;

         bool compatible(self_type const & other) const noexcept
         {
            return ptr == other.ptr;
         }

      public:
         constexpr explicit uuid_const_iterator(pointer ptr, size_t const index) :
            ptr(ptr), index(index)
         {
         }

         uuid_const_iterator(uuid_const_iterator const & o) = default;
         uuid_const_iterator& operator=(uuid_const_iterator const & o) = default;
         ~uuid_const_iterator() = default;

         self_type & operator++ ()
         {
            if (index >= 16)
               throw std::out_of_range("Iterator cannot be incremented past the end of the data.");
            ++index;
            return *this;
         }

         self_type operator++ (int)
         {
            self_type tmp = *this;
            ++*this;
            return tmp;
         }

         bool operator== (self_type const & other) const
         {
            assert(compatible(other));
            return index == other.index;
         }

         bool operator!= (self_type const & other) const
         {
            return !(*this == other);
         }

         reference operator* () const
         {
            if (ptr == nullptr)
               throw std::bad_function_call();
            return *(ptr + index);
         }

         reference operator-> () const
         {
            if (ptr == nullptr)
               throw std::bad_function_call();
            return *(ptr + index);
         }

         uuid_const_iterator() = default;

         self_type & operator--()
         {
            if (index <= 0)
               throw std::out_of_range("Iterator cannot be decremented past the beginning of the data.");
            --index;
            return *this;
         }

         self_type operator--(int)
         {
            self_type tmp = *this;
            --*this;
            return tmp;
         }

         self_type operator+(difference_type offset) const
         {
            self_type tmp = *this;
            return tmp += offset;
         }

         self_type operator-(difference_type offset) const
         {
            self_type tmp = *this;
            return tmp -= offset;
         }

         difference_type operator-(self_type const & other) const
         {
            assert(compatible(other));
            return (index - other.index);
         }

         bool operator<(self_type const & other) const
         {
            assert(compatible(other));
            return index < other.index;
         }

         bool operator>(self_type const & other) const
         {
            return other < *this;
         }

         bool operator<=(self_type const & other) const
         {
            return !(other < *this);
         }

         bool operator>=(self_type const & other) const
         {
            return !(*this < other);
         }

         self_type & operator+=(difference_type const offset)
         {
            if (static_cast<difference_type>(index) + offset < 0 ||
               static_cast<difference_type>(index) + offset > 16)
               throw std::out_of_range("Iterator cannot be incremented outside data bounds.");

            index += offset;
            return *this;
         }

         self_type & operator-=(difference_type const offset)
         {
            return *this += -offset;
         }

         value_type const & operator[](difference_type const offset) const
         {
            return (*(*this + offset));
         }
      };

      using value_type = uint8_t;

   public:
      constexpr uuid() noexcept : data({}) {};

      explicit uuid(gsl::span<value_type, 16> bytes)
      {
         std::copy(std::cbegin(bytes), std::cend(bytes), std::begin(data));
      }

      template<typename ForwardIterator>
      explicit uuid(ForwardIterator first, ForwardIterator last)
      {
         if (std::distance(first, last) == 16)
            std::copy(first, last, std::begin(data));
      }

      constexpr uuid_variant variant() const noexcept
      {
         if ((data[8] & 0x80) == 0x00)
            return uuid_variant::ncs;
         else if ((data[8] & 0xC0) == 0x80)
            return uuid_variant::rfc;
         else if ((data[8] & 0xE0) == 0xC0)
            return uuid_variant::microsoft;
         else
            return uuid_variant::reserved;
      }

      constexpr uuid_version version() const noexcept
      {
         if ((data[6] & 0xF0) == 0x10)
            return uuid_version::time_based;
         else if ((data[6] & 0xF0) == 0x20)
            return uuid_version::dce_security;
         else if ((data[6] & 0xF0) == 0x30)
            return uuid_version::name_based_md5;
         else if ((data[6] & 0xF0) == 0x40)
            return uuid_version::random_number_based;
         else if ((data[6] & 0xF0) == 0x50)
            return uuid_version::name_based_sha1;
         else
            return uuid_version::none;
      }

      constexpr std::size_t size() const noexcept { return 16; }

      constexpr bool is_nil() const noexcept
      {
         for (size_t i = 0; i < data.size(); ++i) if (data[i] != 0) return false;
         return true;
      }

      void swap(uuid & other) noexcept
      {
         data.swap(other.data);
      }

      constexpr uuid_const_iterator begin() const noexcept { return uuid_const_iterator(&data[0], 0); }
      constexpr uuid_const_iterator end() const noexcept { return uuid_const_iterator(&data[0], 16); }

      inline gsl::span<std::byte const, 16> as_bytes() const
      {
         return gsl::span<std::byte const, 16>(reinterpret_cast<std::byte const*>(data.data()), 16);
      }

      template <typename TChar>
      static uuid from_string(TChar const * const str, size_t const size)
      {
         TChar digit = 0;
         bool firstDigit = true;
         int hasBraces = 0;
         size_t index = 0;
         std::array<uint8_t, 16> data{ { 0 } };

         if (str == nullptr || size == 0)
            throw uuid_error{ "Wrong uuid format" };

         if (str[0] == static_cast<TChar>('{'))
            hasBraces = 1;
         if (hasBraces && str[size - 1] != static_cast<TChar>('}'))
            throw uuid_error{ "Wrong uuid format" };

         for (size_t i = hasBraces; i < size - hasBraces; ++i)
         {
            if (str[i] == static_cast<TChar>('-')) continue;

            if (index >= 16 || !detail::is_hex(str[i]))
            {
               throw uuid_error{ "Wrong uuid format" };
            }

            if (firstDigit)
            {
               digit = str[i];
               firstDigit = false;
            }
            else
            {
               data[index++] = detail::hexpair2char(digit, str[i]);
               firstDigit = true;
            }
         }

         if (index < 16)
         {
            throw uuid_error{ "Wrong uuid format" };
         }

         return uuid{ std::cbegin(data), std::cend(data) };
      }

      static uuid from_string(std::string_view str)
      {
         return from_string(str.data(), str.size());
      }

      static uuid from_string(std::wstring_view str)
      {
         return from_string(str.data(), str.size());
      }

   private:
      std::array<value_type, 16> data{ { 0 } };

      friend bool operator==(uuid const & lhs, uuid const & rhs) noexcept;
      friend bool operator<(uuid const & lhs, uuid const & rhs) noexcept;

      template <class Elem, class Traits>
      friend std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id);
   };

   inline bool operator== (uuid const& lhs, uuid const& rhs) noexcept
   {
      return lhs.data == rhs.data;
   }

   inline bool operator!= (uuid const& lhs, uuid const& rhs) noexcept
   {
      return !(lhs == rhs);
   }

   inline bool operator< (uuid const& lhs, uuid const& rhs) noexcept
   {
      return lhs.data < rhs.data;
   }

   template <class Elem, class Traits>
   std::basic_ostream<Elem, Traits> & operator<<(std::basic_ostream<Elem, Traits> &s, uuid const & id)
   {
      return s << std::hex << std::setfill(static_cast<Elem>('0'))
         << std::setw(2) << (int)id.data[0]
         << std::setw(2) << (int)id.data[1]
         << std::setw(2) << (int)id.data[2]
         << std::setw(2) << (int)id.data[3]
         << '-'
         << std::setw(2) << (int)id.data[4]
         << std::setw(2) << (int)id.data[5]
         << '-'
         << std::setw(2) << (int)id.data[6]
         << std::setw(2) << (int)id.data[7]
         << '-'
         << std::setw(2) << (int)id.data[8]
         << std::setw(2) << (int)id.data[9]
         << '-'
         << std::setw(2) << (int)id.data[10]
         << std::setw(2) << (int)id.data[11]
         << std::setw(2) << (int)id.data[12]
         << std::setw(2) << (int)id.data[13]
         << std::setw(2) << (int)id.data[14]
         << std::setw(2) << (int)id.data[15];
   }

   inline std::string to_string(uuid const & id)
   {
      std::stringstream sstr;
      sstr << id;
      return sstr.str();
   }

   inline std::wstring to_wstring(uuid const & id)
   {
      std::wstringstream sstr;
      sstr << id;
      return sstr.str();
   }

   inline void swap(uuids::uuid & lhs, uuids::uuid & rhs)
   {
      lhs.swap(rhs);
   }

   class uuid_system_generator
   {
   public:
      using result_type = uuid;

      uuid operator()()
      {
#ifdef _WIN32

         GUID newId;
         ::CoCreateGuid(&newId);

         std::array<uint8_t, 16> bytes =
         { {
               (unsigned char)((newId.Data1 >> 24) & 0xFF),
               (unsigned char)((newId.Data1 >> 16) & 0xFF),
               (unsigned char)((newId.Data1 >> 8) & 0xFF),
               (unsigned char)((newId.Data1) & 0xFF),

               (unsigned char)((newId.Data2 >> 8) & 0xFF),
               (unsigned char)((newId.Data2) & 0xFF),

               (unsigned char)((newId.Data3 >> 8) & 0xFF),
               (unsigned char)((newId.Data3) & 0xFF),

               newId.Data4[0],
               newId.Data4[1],
               newId.Data4[2],
               newId.Data4[3],
               newId.Data4[4],
               newId.Data4[5],
               newId.Data4[6],
               newId.Data4[7]
            } };

         return uuid{ std::begin(bytes), std::end(bytes) };

#elif defined(__linux__) || defined(__unix__)

         uuid_t id;
         uuid_generate(id);

         std::array<uint8_t, 16> bytes =
         { {
               id[0],
               id[1],
               id[2],
               id[3],
               id[4],
               id[5],
               id[6],
               id[7],
               id[8],
               id[9],
               id[10],
               id[11],
               id[12],
               id[13],
               id[14],
               id[15]
            } };

         return uuid{ std::begin(bytes), std::end(bytes) };

#elif defined(__APPLE__)
         auto newId = CFUUIDCreate(NULL);
         auto bytes = CFUUIDGetUUIDBytes(newId);
         CFRelease(newId);

         std::array<uint8_t, 16> arrbytes =
         { {
               bytes.byte0,
               bytes.byte1,
               bytes.byte2,
               bytes.byte3,
               bytes.byte4,
               bytes.byte5,
               bytes.byte6,
               bytes.byte7,
               bytes.byte8,
               bytes.byte9,
               bytes.byte10,
               bytes.byte11,
               bytes.byte12,
               bytes.byte13,
               bytes.byte14,
               bytes.byte15
            } };
         return uuid{ std::begin(arrbytes), std::end(arrbytes) };
#elif
         return uuid{};
#endif
      }
   };

   template <typename UniformRandomNumberGenerator>
   class basic_uuid_random_generator
   {
   public:
      using result_type = uuid;

      basic_uuid_random_generator()
         :generator(new UniformRandomNumberGenerator)
      {
         std::random_device rd;
         generator->seed(rd());
      }

      explicit basic_uuid_random_generator(UniformRandomNumberGenerator& gen) :
         generator(&gen, [](auto) {}) {}
      explicit basic_uuid_random_generator(UniformRandomNumberGenerator* gen) :
         generator(gen, [](auto) {}) {}

      uuid operator()()
      {
         uint8_t bytes[16];
         for (int i = 0; i < 16; i += 4)
            *reinterpret_cast<uint32_t*>(bytes + i) = distribution(*generator);

         // variant must be 10xxxxxx
         bytes[8] &= 0xBF;
         bytes[8] |= 0x80;

         // version must be 0100xxxx
         bytes[6] &= 0x4F;
         bytes[6] |= 0x40;

         return uuid{std::begin(bytes), std::end(bytes)};
      }

   private:
      std::uniform_int_distribution<uint32_t>  distribution;
      std::shared_ptr<UniformRandomNumberGenerator> generator;
   };

   using uuid_random_generator = basic_uuid_random_generator<std::mt19937>;

   class uuid_name_generator
   {
   public:
      using result_type = uuid;

      explicit uuid_name_generator(uuid const& namespace_uuid) noexcept
         : nsuuid(namespace_uuid)
      {}

      uuid operator()(std::string_view name)
      {
         reset();
         process_characters(name.data(), name.size());
         return make_uuid();
      }

      uuid operator()(std::wstring_view name)
      {
         reset();
         process_characters(name.data(), name.size());
         return make_uuid();
      }

   private:
      void reset()
      {
         hasher.reset();
         uint8_t bytes[16];
         std::copy(std::begin(nsuuid), std::end(nsuuid), bytes);
         hasher.process_bytes(bytes, 16);
      }

      template <typename char_type,
                typename = std::enable_if_t<std::is_integral<char_type>::value>>
      void process_characters(char_type const * const characters, size_t const count)
      {
         for (size_t i = 0; i < count; i++)
         {
            uint32_t c = characters[i];
            hasher.process_byte(static_cast<unsigned char>((c >> 0) & 0xFF));
            hasher.process_byte(static_cast<unsigned char>((c >> 8) & 0xFF));
            hasher.process_byte(static_cast<unsigned char>((c >> 16) & 0xFF));
            hasher.process_byte(static_cast<unsigned char>((c >> 24) & 0xFF));
         }
      }

      void process_characters(const char * const characters, size_t const count)
      {
         hasher.process_bytes(characters, count);
      }

      uuid make_uuid()
      {
         detail::sha1::digest8_t digest;
         hasher.get_digest_bytes(digest);

         // variant must be 0b10xxxxxx
         digest[8] &= 0xBF;
         digest[8] |= 0x80;

         // version must be 0b0101xxxx
         digest[6] &= 0x5F;
         digest[6] |= 0x50;

         return uuid{ digest, digest + 16 };
      }

   private:
      uuid nsuuid;
      detail::sha1 hasher;
   };
}

namespace std
{
   template <>
   struct hash<uuids::uuid>
   {
      using argument_type = uuids::uuid;
      using result_type   = std::size_t;

      result_type operator()(argument_type const &uuid) const
      {
         std::hash<std::string> hasher;
         return static_cast<result_type>(hasher(uuids::to_string(uuid)));
      }
   };
}


/*
 * This file is part of the Visual Computing Library (VCL) release under the
 * license.
 *
 * Copyright (c) 2017 Basil Fierz
 *
 * 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

// VCL configuration
#include <vcl/config/global.h>

// C++ Standard library
#include <memory>
#include <vector>

// GSL
#include <gsl/gsl>

// VCL
#include <vcl/hid/windows/hid.h>
#include <vcl/hid/spacenavigator.h>

namespace Vcl { namespace HID { namespace Windows
{
	//! Implementation of a 3Dconnexion space mouse
	class SpaceNavigatorHID : public AbstractHID, public SpaceNavigator
	{
	private:
		struct InputData
		{
			//! Current time to live for telling
			//! if the device was unplugged while sending data
			int timeToLive;

			//! Indicate if the data is dirty
			bool isDirty;

			//! Axis data
			std::array<float,6> axes;

			//! Check if the data is zero
			bool isZero()
			{
				return (0 == axes[0] && 0 == axes[1] && 0 == axes[2] &&
						0 == axes[3] && 0 == axes[4] && 0 == axes[5]    );
			}

			//! Maximum time to live
			static const int MaxTimeToLive = 5;
		};

	public:
		SpaceNavigatorHID(std::unique_ptr<GenericHID> dev, bool poll_3d_mouse = false);

		//! Reset device when activating the program
		void onActivateApp(BOOL active, DWORD dwThreadID);

		//! Handle device input
		bool processInput(HWND window_handle, UINT input_code, PRAWINPUT raw_input) override;

	private:

		/*!
		 * \brief Does all the preprocessing of the rawinput device data before
		 *        finally calling the move3D method.
		 *
		 * If polling is enabled (_poll3DMouse == true) this method is called
		 * from the windows timer message handler
		 * If polling is not enabled (_poll3DMouse == false) this method is
		 * called directly from the WM_INPUT handler
		 */
		void on3DMouseInput();

		/*!
		 * \brief onSpaceMouseMove is invoked when new 3d mouse data is
		 *        available.
		 *
		 * \param motion_data Contains the displacement data, using a
		 *                    right-handed coordinate system with z down.
		 *                    See 'Programing for the 3dmouse' document
		 *                    available at www.3dconnexion.com.
		 *                    Entries 0, 1, 2 is the incremental pan zoom
		 *                    displacement vector (x,y,z).
		 *                    Entries 3, 4, 5 is the incremental rotation vector
		 *                    (NOT Euler angles).
		 */
		void onSpaceMouseMove(std::array<float, 6> motion_data);

		/*!
		 * \brief onSpaceMouseKeyDown processes the 3d mouse key presses
		 *
		 * \param virtual_key 3d mouse key code
		 */
		void onSpaceMouseKeyDown(UINT virtual_key);

		/*!
		 * \brief onSpaceMouseKeyUp processes the 3d mouse key releases
		 *
		 * \param virtual_key 3d mouse key code
		 */
		void onSpaceMouseKeyUp(UINT virtual_key);

	private:
		//! Process a raw input message
		bool translateRawInputData(UINT input_code, PRAWINPUT raw_input);

		//! Axis input data
		InputData _deviceData;

		//! Button input data
		uint32_t _keystate{ 0 };

		//! Last time the data was updated.
		//! Use to calculate distance traveled since last event
		DWORD _last3DMouseInputTime{ 0 };

	private: // Polling support methods

		//! Start the timer
		void startTimer(HWND hwnd);

		//! Timer callback
		void onTimer(UINT_PTR event_id);

		//! Kill the currently running timer
		void killPollingTimer();

	private: // Polling support
		//! 3D mouse is in polling mode
		bool _poll3DMouse{ false };

		//! Polling period. Default is 50 Hz)
		UINT _pollingPeriod3DMouse{ 20 };

		//! 3DMouse data polling timer
		//! Only used if _poll3DMouse == true
		UINT_PTR _timer3DMouse{ 0 };
	};
}}}


/*
 * Copyright (c) 2016-2021 lymastee, All rights reserved.
 * Contact: lymastee@hotmail.com
 *
 * This file is part of the gslib project.
 *
 * 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

#ifndef rose_fbed1fbf_2ba3_46bc_97da_b1df11752358_h
#define rose_fbed1fbf_2ba3_46bc_97da_b1df11752358_h

#include <gslib/utility.h>
#include <ariel/config.h>
#include <ariel/rendersys.h>
#include <ariel/painter.h>
#include <ariel/batch.h>
#include <ariel/texbatch.h>

__ariel_begin__

class rose;

/* this block was used for synchronize with the constant buffer */
struct rose_configs
{
    vec4                screen;
    float               mapscreen[3][4];
    /* more.. */
};

struct rose_bind_info_cr
{
    vec4                color;      /* RGBA */
};

struct rose_bind_info_tex
{
    texture2d*          img;
    vec2                tex;
};

struct vertex_info_cr
{
    vec2                pos;
    vec4                cr;
};

struct vertex_info_klm_cr
{
    vec2                pos;
    vec3                klm;
    vec4                cr;
};

struct vertex_info_coef_cr
{
    vec2                pos;
    vec4                coef;       /* float3 coef & float tune */
    vec4                cr;
};

struct vertex_info_klm_tex
{
    vec2                pos;
    vec3                klm;
    vec2                tex;
};

struct vertex_info_coef_tex
{
    vec2                pos;
    vec4                coef;
    vec2                tex;
};

class rose_batch;
typedef list<rose_bind_info_cr> rose_bind_list_cr;
typedef list<rose_bind_info_tex> rose_bind_list_tex;
typedef vector<rose_batch*> rose_batch_list;
typedef vector<vertex_info_cr> vertex_stream_cr;
typedef vector<vertex_info_klm_cr> vertex_stream_klm_cr;
typedef vector<vertex_info_coef_cr> vertex_stream_cf_cr;
typedef vector<vertex_info_klm_tex> vertex_stream_klm_tex;
typedef vector<vertex_info_coef_tex> vertex_stream_cf_tex;
typedef vector<int> index_stream;
typedef bat_type rose_batch_tag;

class __gs_novtable rose_batch abstract
{
public:
    typedef rendersys::vertex_buffer vertex_buffer;
    typedef rendersys::index_buffer index_buffer;

public:
    rose_batch(int index);
    virtual ~rose_batch();
    virtual rose_batch_tag get_tag() const = 0;
    virtual void create(bat_batch* bat) = 0;
    virtual int buffering(rendersys* rsys) = 0;
    virtual void draw(rendersys* rsys) = 0;
    virtual void tracing() const = 0;

protected:
    int                 _bat_index;
    vertex_shader*      _vertex_shader;
    pixel_shader*       _pixel_shader;
    vertex_format*      _vertex_format;
    vertex_buffer*      _vertex_buffer;

public:
    int get_batch_index() const { return _bat_index; }
    void set_vertex_shader(vertex_shader* p) { _vertex_shader = p; }
    void set_pixel_shader(pixel_shader* p) { _pixel_shader = p; }
    void set_vertex_format(vertex_format* p) { _vertex_format = p; }
    void setup_vs_and_ps(rendersys* rsys);
    void setup_vf_and_topology(rendersys* rsys, uint topo);

protected:
    template<class stream_type>
    int template_buffering(stream_type& stm, rendersys* rsys);
};

class rose_fill_batch_cr:
    public rose_batch
{
public:
    rose_fill_batch_cr(int index): rose_batch(index) {}
    rose_batch_tag get_tag() const override { return bf_cr; }
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;
    void draw(rendersys* rsys) override;
    void tracing() const override;

protected:
    vertex_stream_cr    _vertices;

private:
    void create_from_fill(bat_fill_batch* bat);
    void create_from_stroke(bat_stroke_batch* bat);
};

class rose_fill_batch_klm_cr:
    public rose_batch
{
public:
    rose_fill_batch_klm_cr(int index) : rose_batch(index) {}
    rose_batch_tag get_tag() const override { return bf_klm_cr; }
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;
    void draw(rendersys* rsys) override;
    void tracing() const override;

protected:
    vertex_stream_klm_cr _vertices;
};

class rose_fill_batch_klm_tex:
    public rose_batch
{
    friend class rose_stroke_batch_assoc_with_klm_tex;

public:
    rose_fill_batch_klm_tex(int index, render_sampler_state* ss);
    ~rose_fill_batch_klm_tex() { destroy(); }
    rose_batch_tag get_tag() const override { return bf_klm_tex; }
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;
    void draw(rendersys* rsys) override;
    void tracing() const override;
    void destroy();

protected:
    vertex_stream_klm_tex _vertices;
    tex_batcher         _texbatch;
    render_sampler_state* _sstate;
    shader_resource_view* _srv;
    render_texture2d*   _tex;

private:
    void create_from_fill(bat_fill_batch* bat);
    void create_from_stroke(bat_stroke_batch* bat);
};

class rose_stroke_batch_coef_cr:
    public rose_batch
{
public:
    rose_stroke_batch_coef_cr(int index) : rose_batch(index) {}
    rose_batch_tag get_tag() const override { return bs_coef_cr; }
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;
    void draw(rendersys* rsys) override;
    void tracing() const override;

protected:
    vertex_stream_cf_cr _vertices;
};

class rose_stroke_batch_coef_tex:
    public rose_batch
{
public:
    rose_stroke_batch_coef_tex(int index, render_sampler_state* ss);
    ~rose_stroke_batch_coef_tex() { destroy(); }
    rose_batch_tag get_tag() const override { return bs_coef_tex; }
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;
    void draw(rendersys* rsys) override;
    void tracing() const override;
    void destroy();

protected:
    vertex_stream_cf_tex _vertices;
    tex_batcher         _texbatch;
    render_sampler_state* _sstate;
    shader_resource_view* _srv;
    render_texture2d*   _tex;

protected:
    void create_vertices(bat_batch* bat);
    void create_vertices(bat_lines& lines, const tex_batcher& bat);
};

class rose_stroke_batch_assoc_with_klm_tex:
    public rose_stroke_batch_coef_tex
{
public:
    rose_stroke_batch_assoc_with_klm_tex(int index, rose_fill_batch_klm_tex* assoc);
    ~rose_stroke_batch_assoc_with_klm_tex();
    void create(bat_batch* bat) override;
    int buffering(rendersys* rsys) override;

protected:
    rose_fill_batch_klm_tex* _assoc;

protected:
    void create_vertices(bat_batch* bat);
};

class rose_bindings
{
public:
    rose_bind_list_cr& get_cr_bindings() { return _cr_bindings; }
    rose_bind_list_tex& get_tex_bindings() { return _tex_bindings; }
    void clear_binding_cache();

protected:
    rose_bind_list_cr   _cr_bindings;
    rose_bind_list_tex  _tex_bindings;
};

class graphics_obj_entity:
    public loop_blinn_processor
{
public:
    graphics_obj_entity(float w, float h) : loop_blinn_processor(w, h) {}
    void proceed_fill(const painter_path& path) { __super::proceed(path); }
    void proceed_stroke(const painter_path& path);

protected:
    int create_from_path(const painter_path& path, int start);

    struct path_seg
    {
        lb_line*        first;
        lb_line*        last;
        path_seg() { first = last = nullptr; }
    };
    void add_line_seg(path_seg& seg, const painter_path::line_to_node* node);
    void add_quad_seg(path_seg& seg, const painter_node* node1, const painter_path::quad_to_node* node2);
    void add_cubic_seg(path_seg& seg, const painter_node* node1, const painter_path::cubic_to_node* node2);
};

class graphics_obj:
    public std::shared_ptr<graphics_obj_entity>
{
public:
    graphics_obj(float w, float h);
};

extern void rose_paint_non_picture_brush(graphics_obj& gfx, rose_bindings& bindings, const painter_brush& brush);
extern void rose_paint_solid_brush(graphics_obj& gfx, rose_bind_list_cr& bind_cache, const painter_brush& brush);
extern void rose_paint_picture_brush(graphics_obj& gfx, const rectf& bound, rose_bind_list_tex& bind_cache, const painter_brush& brush);
extern void rose_paint_pen(graphics_obj& gfx, const painter_path& path, rose_bindings& bindings, const painter_pen& pen);
extern void rose_paint_solid_pen(graphics_obj& gfx, rose_bind_list_cr& bind_cache, const painter_pen& pen);
extern void rose_paint_picture_pen(graphics_obj& gfx, const rectf& bound, rose_bind_list_tex& bind_cache, const painter_pen& pen);
/* more to come. */

class rose:
    public painter
{
public:
    typedef render_constant_buffer constant_buffer;
    typedef render_sampler_state sampler_state;
    typedef list<graphics_obj> graphics_obj_cache;

public:
    rose();
    virtual ~rose();
    virtual void resize(int w, int h) override {}
    virtual void draw_path(const painter_path& path) override;
    virtual void on_draw_begin() override;
    virtual void on_draw_end() override;

public:
    void setup(rendersys* rsys);
    rendersys* get_rendersys() const { return _rsys; }
    void fill_non_picture_graphics_obj(graphics_obj& gfx, uint brush_tag);
    bat_batch* fill_picture_graphics_obj(graphics_obj& gfx);
    void stroke_graphics_obj(graphics_obj& gfx, uint pen_tag);

protected:
    template<class _addline>
    void meta_stroke_graphics_obj(graphics_obj& gfx, _addline fn_add);

protected:
    rendersys*          _rsys;
    rose_configs        _cfgs;
    batch_processor     _bp;
    rose_batch_list     _batches;
    rose_bindings       _bindings;
    graphics_obj_cache  _gocache;
    float               _nextz;

protected:
    void setup_configs();
    void prepare_fill(const painter_path& path, const painter_brush& brush);
    void prepare_picture_fill(const painter_path& path, const painter_brush& brush);
    void prepare_stroke(const painter_path& path, const painter_pen& pen);
    rose_batch* create_fill_batch_cr(int index);
    rose_batch* create_fill_batch_klm_cr(int index);
    rose_batch* create_fill_batch_klm_tex(int index);
    rose_batch* create_stroke_batch_cr(int index);
    rose_batch* create_stroke_batch_tex(int index);
    rose_batch* create_stroke_batch_assoc(int index, rose_fill_batch_klm_tex* assoc);
    void clear_batches();
    void prepare_batches();
    void draw_batches();

#if use_rendersys_d3d_11
protected:
    void initialize();
    void destroy_miscs();
    sampler_state* acquire_default_sampler_state();

protected:
    vertex_shader*      _vsf_cr;
    vertex_shader*      _vsf_klm_cr;
    vertex_shader*      _vsf_klm_tex;
    pixel_shader*       _psf_cr;
    pixel_shader*       _psf_klm_cr;
    pixel_shader*       _psf_klm_tex;
    vertex_format*      _vf_cr;
    vertex_format*      _vf_klm_cr;
    vertex_format*      _vf_klm_tex;
    vertex_shader*      _vss_coef_cr;
    vertex_shader*      _vss_coef_tex;
    pixel_shader*       _pss_coef_cr;
    pixel_shader*       _pss_coef_tex;
    vertex_format*      _vf_coef_cr;
    vertex_format*      _vf_coef_tex;
    sampler_state*      _sampler_state;
    constant_buffer*    _cb_configs;
    uint                _cb_config_slot;
#endif
};

__ariel_end__

#endif


///
/// @file
///
/// @author Mirko Myllykoski (mirkom@cs.umu.se), Umeå University
/// @author Lars Karlsson (larsk@cs.umu.se), Umeå University
///
/// @internal LICENSE
///
/// Copyright (c) 2019-2020, Umeå Universitet
///
/// Redistribution and use in source and binary forms, with or without
/// modification, are permitted provided that the following conditions are met:
///
/// 1. Redistributions of source code must retain the above copyright notice,
///    this list of conditions and the following disclaimer.
///
/// 2. Redistributions in binary form must reproduce the above copyright notice,
///    this list of conditions and the following disclaimer in the documentation
///    and/or other materials provided with the distribution.
///
/// 3. Neither the name of the copyright holder nor the names of its
///    contributors may be used to endorse or promote products derived from this
///    software without specific prior written permission.
///
/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
/// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
/// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
/// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
/// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
/// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
/// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
/// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
/// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
/// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
/// POSSIBILITY OF SUCH DAMAGE.
///

#include <starneig_config.h>
#include <starneig/configuration.h>
#include "node_internal.h"
#ifdef STARNEIG_ENABLE_MPI
#include "../mpi/node_internal.h"
#include "../mpi/distr_matrix_internal.h"
#endif
#include "common.h"
#include "scratch.h"
#include <starneig/node.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <hwloc.h>
#include <starpu.h>
#ifdef MKL_SET_NUM_THREADS_LOCAL_FOUND
#include <mkl.h>
#endif
#if defined(OPENBLAS_SET_NUM_THREADS_FOUND) || \
defined(GOTO_SET_NUM_THREADS_FOUND)
#include <cblas.h>
#endif

static struct {
    /// initialization flag
    bool is_init;
    // initialization flags
    starneig_flag_t flags;
    /// library mode
    enum starneig_mode mode;
    /// blas mode
    enum starneig_blas_mode blas_mode;
    // original blas thread count
    int blas_threads_original;
    // StarPU worker bind mask
    unsigned starpu_workers_bindid[STARPU_NMAXWORKERS];
    // total number of available cpu cores
    int avail_cores;
    // total number of available gpus
    int avail_gpus;
    // number of used cpu cores
    int used_cores;
    // total number of used gpus
    int used_gpus;
} state = {
    .is_init = false,
    .flags = STARNEIG_DEFAULT,
    .mode = STARNEIG_MODE_OFF,
    .blas_mode = STARNEIG_BLAS_MODE_ORIGINAL,
    .blas_threads_original = -1,
    .avail_cores = 0,
    .avail_gpus = 0,
    .used_cores = 0,
    .used_gpus = 0
};

///
/// @brief Sets the number of BLAS threads.
///
/// @param[in] threads
///         Number of BLAS threads.
///
/// @return Previous BLAS thread count (can be 0).
///
static int set_blas_threads(int threads)
{
    starneig_verbose("Setting BLAS thread count to %d.", threads);
#ifdef MKL_SET_NUM_THREADS_LOCAL_FOUND
    return mkl_set_num_threads_local(threads);
#elif defined(OPENBLAS_SET_NUM_THREADS_FOUND)
    int old = openblas_get_num_threads();
    openblas_set_num_threads(threads);
    return old;
#elif defined(GOTO_SET_NUM_THREADS_FOUND)
    goto_set_num_threads(threads);
    return 1;
#else
    return 1;
#endif
}

///
/// @brief Sets the BLAS mode.
///
/// @param[in] mode
///         BLAS mode.
///
static void set_blas_mode(enum starneig_blas_mode mode)
{
    int old = -1;
    switch (mode) {
        case STARNEIG_BLAS_MODE_PARALLEL:
            starneig_verbose("Switching to parallel BLAS.");
            old = set_blas_threads(state.used_cores);
            break;
        case STARNEIG_BLAS_MODE_SEQUENTIAL:
            starneig_verbose("Switching to sequential BLAS.");
            old = set_blas_threads(1);
            break;
        default:
            starneig_verbose("Restoring BLAS mode.");
            if (0 <= state.blas_threads_original)
                set_blas_threads(state.blas_threads_original);
            state.blas_threads_original = -1;
    }
    if (state.blas_mode == STARNEIG_BLAS_MODE_ORIGINAL && 0 <= old)
        state.blas_threads_original = old;
    state.blas_mode = mode;
}

#ifdef MKL_SET_NUM_THREADS_LOCAL_FOUND

///
/// @brief Sets the per thread BLAS thread count to 1.
///
/// @param[in] arg
///         An unused argument.
///
static void set_worker_blas_mode(void *arg)
{
    mkl_set_num_threads_local(1);
}

#endif

///
/// @brief Reconfigures the node.
///
/// @param[in] cores
///         Number of CPU cores to use.
///
/// @param[in] gpus
///         Number of GPUs to use.
///
/// @param[in] mode
///         Library mode.
///
/// @param[in] blas_mode
///         BLAS mode.
///
#define CONFIGURE(cores, gpus, mode, blas_mode) \
    node_configure(cores, gpus, mode, blas_mode, __func__)

///
/// @brief Reconfigures the node.
///
/// @param[in] cores
///         Number of CPU cores to use.
///
/// @param[in] gpus
///         Number of GPUs to use.
///
/// @param[in] mode
///         Library mode.
///
/// @param[in] blas_mode
///         BLAS mode
///
/// @param[in] func
///         Name of the calling function.
///
static void node_configure(
    int cores, int gpus, enum starneig_mode mode,
    enum starneig_blas_mode blas_mode, char const *func)
{
#ifndef STARNEIG_ENABLE_MPI
    if (mode == STARNEIG_MODE_DM)
        starneig_fatal_error("StarPU was compiled without MPI support.");
#endif

    if (cores == state.used_cores && gpus == state.used_gpus &&
    mode == state.mode && blas_mode == state.blas_mode)
        return;

    starneig_verbose("Reconfiguring the library.");

    if (cores == state.used_cores && gpus == state.used_gpus &&
    mode == state.mode) {
        set_blas_mode(blas_mode);
        return;
    }

    //
    // shutdown StarPU
    //

    if (state.mode != STARNEIG_MODE_OFF) {
        starneig_node_resume_starpu();
#ifdef STARNEIG_ENABLE_CUDA
        if (0 < state.used_gpus) {
            starneig_verbose("Shutting down cuBLAS.");
            starpu_cublas_shutdown();
        }
#endif

        starneig_verbose("Shutting down StarPU.");

        starneig_scratch_unregister();
#ifdef STARNEIG_ENABLE_MPI
        starneig_mpi_cache_clear();
    if (state.mode == STARNEIG_MODE_DM &&
    state.flags & STARNEIG_AWAKE_MPI_WORKER)
        starneig_mpi_stop_persistent_starpumpi();
#endif

        starpu_task_wait_for_all();
        starpu_shutdown();
    }

    //
    // set the number of CPU cores
    //

    if (cores == 0)
        starneig_fatal_error("At least one CPU core must be selected.");

    if (cores < 0) {
        state.used_cores = state.avail_cores;
    }
    else {
        state.used_cores = MIN(cores, state.avail_cores);
        if (state.avail_cores < cores)
            starneig_warning(
                "Failed to acquire the desired number of CPU cores. "
                "Acquired %d.", state.used_cores);
    }

    //
    // set the number of GPUs
    //

    if (gpus < 0) {
        state.used_gpus = state.avail_gpus;
    }
    else {
#ifdef STARNEIG_ENABLE_CUDA
        state.used_gpus = MIN(gpus, state.avail_gpus);

        if (state.avail_gpus < gpus)
            starneig_warning(
                "Failed to acquire the desired number of CUDA devices. "
                "Acquired %d.", state.used_gpus);
#else
        if (0 < gpus)
            starneig_warning("StarPU was compiled without CUDA support.");
#endif
    }

    //
    // set BLAS threads
    //

    set_blas_mode(blas_mode);

    //
    // set mode
    //

    state.mode = mode;
    if (state.mode == STARNEIG_MODE_OFF)
        return;

    //
    // create StarPU configuration
    //

    starneig_verbose("Configuring StarPU.");

    struct starpu_conf conf;
    starpu_conf_init(&conf);

    int cpu_workers = state.used_cores;
    if (0 < state.used_gpus)
        cpu_workers -= state.used_gpus;
    if (state.mode == STARNEIG_MODE_DM)
        cpu_workers--;

    conf.ncpus = MAX(1, cpu_workers);
    conf.ncuda = state.used_gpus;
    conf.nopencl = 0;

//#if 1 < STARPU_MAJOR_VERSION || 2 < STARPU_MINOR_VERSION
    if (getenv("STARPU_WORKERS_CPUID") == NULL)
        conf.use_explicit_workers_bindid = 1;
//#endif
    memcpy(conf.workers_bindid, state.starpu_workers_bindid,
        sizeof(state.starpu_workers_bindid));

#ifdef STARNEIG_ENABLE_CUDA
    if (0 < state.used_gpus)
        conf.sched_policy_name = "dmdas";
    else
#endif
        conf.sched_policy_name = "prio";

    //
    // setup FXT
    //

    if (state.flags & STARNEIG_FXT_DISABLE) {
        starneig_verbose("Disabling FXT traces.");
        starpu_fxt_autostart_profiling(0);
    }
    else {
        char const *starpu_fxt_trace = getenv("STARPU_FXT_TRACE");
        if (starpu_fxt_trace == NULL || atoi(starpu_fxt_trace) != 0) {
            starneig_verbose("Keeping FXT traces enabled.");
            starpu_fxt_autostart_profiling(1);
        }
    }

    //
    // initialize StarPU
    //

    starneig_verbose("Starting StarPU.");

    unsigned seed = rand();
    int ret = starpu_init(&conf);
    srand(seed);

    if (ret != 0)
        starneig_fatal_error("Failed to initialize StarPU.");

    starpu_profiling_status_set(STARPU_PROFILING_ENABLE);
    starpu_malloc_set_align(64);

    //
    // initialize persistent StarPU-MPI
    //

#ifdef STARNEIG_ENABLE_MPI
    if (state.mode == STARNEIG_MODE_DM &&
    state.flags & STARNEIG_AWAKE_MPI_WORKER)
        starneig_mpi_start_persistent_starpumpi();
#endif

    //
    // cuBLAS
    //

    if (0 < state.used_gpus) {
        starneig_verbose("Initializing cuBLAS.");
        starpu_cublas_init();
    }

    //
    // configure workers
    //

#ifdef MKL_SET_NUM_THREADS_LOCAL_FOUND
    starneig_verbose(
        "MKL detected. Setting StarPU worker BLAS thread count to 1.");
    starpu_execute_on_each_worker(
        &set_worker_blas_mode, NULL, STARPU_CPU | STARPU_CUDA);
#endif

    starneig_node_pause_starpu();
}

void starneig_node_pause_starpu()
{
    if (state.flags & STARNEIG_AWAKE_WORKERS)
        return;

    starneig_verbose("Pausing StarPU workers.");
    starpu_pause();
}

void starneig_node_resume_starpu()
{
    if (state.flags & STARNEIG_AWAKE_WORKERS)
        return;

    starneig_verbose("Waking up StarPU workers.");
    starpu_resume();
}

void starneig_node_pause_awake_starpu()
{
    if (!(state.flags & STARNEIG_AWAKE_WORKERS))
        return;

    starneig_verbose("Pausing \"awake\" StarPU workers.");
    starpu_pause();
}

void starneig_node_resume_awake_starpu()
{
    if (!(state.flags & STARNEIG_AWAKE_WORKERS))
        return;

    starneig_verbose("Waking up \"awake\" StarPU workers.");
    starpu_resume();
}

__attribute__ ((visibility ("default")))
void starneig_node_init(int cores, int gpus, starneig_flag_t flags)
{
    starneig_set_message_mode(
        !(flags & STARNEIG_NO_MESSAGES), !(flags & STARNEIG_NO_VERBOSE));

    starneig_verbose("Initializing node.");

    if (state.is_init)
        starneig_fatal_error("The node is already initialized.");

    state.flags = flags;

    //
    // set up CUDA
    //

    state.avail_gpus = 0;
    state.used_gpus = 0;

#ifdef STARNEIG_ENABLE_CUDA
    // query the number of available CUDA devices
	if (cudaGetDeviceCount(&state.avail_gpus) != cudaSuccess) {
        starneig_warning("Failed to acquire CUDA device count.");
        state.avail_gpus = 0;
    }
#endif

    // query STARPU_NCUDA environment
    char *starpu_ncuda = getenv("STARPU_NCUDA");
    int num_starpu_gpus = (starpu_ncuda ? atoi(starpu_ncuda) : -1);

    if (num_starpu_gpus != -1) {
        if (state.avail_gpus < num_starpu_gpus)
        starneig_warning(
            "A conflict between STARPU_NCUDA and cudaGetDeviceCount().");
        state.avail_gpus = MIN(state.avail_gpus, num_starpu_gpus);
    }

    //
    // set up CPU cores
    //

    state.avail_cores = 0;
    state.used_cores = 0;

    // query the SLURM environment
    char *slurm = getenv("SLURM_CPUS_PER_TASK");
    const int num_slurm_cpus = (slurm ? atoi(slurm) : -1);

    // query STARPU_NCPUS environment
    char *starpu_ncpus = getenv("STARPU_NCPUS");
    int num_starpu_cpus = -1;
    if (starpu_ncpus) {
        if (0 < state.avail_gpus)
            num_starpu_cpus = atoi(starpu_ncpus) + 1;
        else
            num_starpu_cpus = atoi(starpu_ncpus);
    }

    // query hardware topology and CPU core binding mask

    hwloc_topology_t topology;
    hwloc_topology_init(&topology);
    hwloc_topology_load(topology);

    hwloc_cpuset_t res = hwloc_bitmap_alloc();

    hwloc_cpuset_t mask = hwloc_bitmap_alloc();
    hwloc_get_cpubind(topology, mask, HWLOC_CPUBIND_THREAD);

    int depth_cores = hwloc_get_type_depth(topology, HWLOC_OBJ_CORE);
    int num_cores = hwloc_get_nbobjs_by_depth(topology, depth_cores);

    int num_hwloc_cpus = 0;

    // iterate over all COREs
    for (int i = 0; i < num_cores && num_hwloc_cpus < STARPU_NMAXWORKERS; i++) {
        hwloc_obj_t core = hwloc_get_obj_by_depth(topology, depth_cores, i);

        // if the CORE has PUs inside it, ...
        if (core->first_child && core->first_child->type == HWLOC_OBJ_PU) {
            // iterate over them
            hwloc_obj_t pu = core->first_child;
            while (pu) {
                // if the PU is in the binding mask, ...
                hwloc_bitmap_and(res, mask, pu->cpuset);
                if (!hwloc_bitmap_iszero(res)) {
                    // add it to the worker list
                    state.starpu_workers_bindid[num_hwloc_cpus++] =
                        pu->logical_index;
                    break;
                }
                pu = pu->next_sibling;
            }
        }
        else {
            // if the CORE is in the binding mask, ...
            hwloc_bitmap_and(res, mask, core->cpuset);
            if (!hwloc_bitmap_iszero(res)) {
                // add it to the worker list
                state.starpu_workers_bindid[num_hwloc_cpus++] =
                    core->logical_index;
            }
        }
    }

    hwloc_bitmap_free(mask);
    hwloc_bitmap_free(res);
    hwloc_topology_destroy(topology);

    starneig_verbose_begin("Attached CPU cores");
    for (int i = 0; i < num_hwloc_cpus; i++)
        starneig_verbose_cont(" %d", state.starpu_workers_bindid[i]);
    starneig_verbose_cont(".\n");

    // set avail_cores

    state.avail_cores = num_hwloc_cpus;
    if (0 < num_starpu_cpus) {
        if (num_hwloc_cpus < num_starpu_cpus)
            starneig_warning(
                "A conflict between STARPU_NCPUS/STARPU_NCUDA and hwloc core "
                "binding mask.");
        state.avail_cores = MIN(state.avail_cores, num_starpu_cpus);
        if (0 < num_slurm_cpus) {
            if (num_slurm_cpus < num_starpu_cpus)
                starneig_warning(
                    "A conflict between STARPU_NCPUS/STARPU_NCUDA and "
                    "SLURM_CPUS_PER_TASK.");
            state.avail_cores = MIN(state.avail_cores, num_slurm_cpus);
        }
    }
    if (0 < num_slurm_cpus) {
        if (num_hwloc_cpus < num_slurm_cpus)
            starneig_warning(
                "A conflict between SLURM_CPUS_PER_TASK and hwloc core "
                "binding mask.");
        state.avail_cores = MIN(state.avail_cores, num_slurm_cpus);
    }

    if (state.avail_cores <= 0)
        starneig_fatal_error("Something unexpected happened.");

    state.is_init   = true;

    if (state.flags & STARNEIG_HINT_DM)
        CONFIGURE(cores, gpus, STARNEIG_MODE_DM, STARNEIG_BLAS_MODE_SEQUENTIAL);
    else
        CONFIGURE(cores, gpus, STARNEIG_MODE_SM, STARNEIG_BLAS_MODE_SEQUENTIAL);
}

__attribute__ ((visibility ("default")))
int starneig_node_initialized()
{
    return state.is_init;
}

__attribute__ ((visibility ("default")))
void starneig_node_finalize(void)
{
    CHECK_INIT();

    starneig_verbose("De-initializing node.");

    CONFIGURE(-1, -1, STARNEIG_MODE_OFF, STARNEIG_BLAS_MODE_ORIGINAL);

    starneig_set_message_mode(0, 0);

    state.avail_cores = 0;
    state.avail_gpus = 0;

    state.is_init = false;
}

__attribute__ ((visibility ("default")))
int starneig_node_get_cores(void)
{
    CHECK_INIT();
    return state.used_cores;
}

__attribute__ ((visibility ("default")))
void starneig_node_set_cores(int cores)
{
    CHECK_INIT();
    CONFIGURE(cores, starneig_node_get_gpus(), state.mode, state.blas_mode);
}

__attribute__ ((visibility ("default")))
int starneig_node_get_gpus(void)
{
    CHECK_INIT();
    return state.used_gpus;
}

__attribute__ ((visibility ("default")))
void starneig_node_set_gpus(int gpus)
{
    CHECK_INIT();
    CONFIGURE(starneig_node_get_cores(), gpus, state.mode, state.blas_mode);
}

__attribute__ ((visibility ("default")))
void starneig_node_set_mode(enum starneig_mode mode)
{
    CHECK_INIT();
    CONFIGURE(starneig_node_get_cores(), starneig_node_get_gpus(),
        mode, state.blas_mode);
}

void starneig_node_set_blas_mode(enum starneig_blas_mode blas_mode)
{
    CHECK_INIT();
    CONFIGURE(starneig_node_get_cores(), starneig_node_get_gpus(),
        state.mode, blas_mode);
}


/******************************************************************************
 *
 * INTEL CONFIDENTIAL
 *
 * Copyright 2015 Intel Corporation All Rights Reserved.
 *
 * The source code contained or described herein and all documents related
 * to the source code (Material) are owned by Intel Corporation or its
 * suppliers or licensors. Title to the Material remains with
 * Intel Corporation or its suppliers and licensors. The Material contains
 * trade secrets and proprietary and confidential information of Intel or
 * its suppliers and licensors. The Material is protected by worldwide
 * copyright and trade secret laws and treaty provisions. No part of the
 * Material may be used, copied, reproduced, modified, published, uploaded,
 * posted, transmitted, distributed, or disclosed in any way without Intel's
 * prior express written permission.
 *
 * No license under any patent, copyright, trade secret or other intellectual
 * property right is granted to or conferred upon you by disclosure or
 * delivery of the Materials, either expressly, by implication, inducement,
 * estoppel or otherwise. Any license under such intellectual property rights
 * must be express and approved by Intel in writing.
 *
 *
 *   Workfile:   FwUpdateDebug.c
 *
 *   Abstract:   macros to wrap dbglog for simple FwUpdate debugging
 *
 ******************************************************************************/

#ifndef __FW_UPDATE_DEBUG_H__
#define __FW_UPDATE_DEBUG_H__

#include <gsl/span>
#include <iostream>

typedef enum
{
    PRINT_NONE = 0,
    PRINT_CRITICAL = 1,
    PRINT_ERROR,
    PRINT_WARNING,
    PRINT_INFO,
    PRINT_DEBUG,
    PRINT_DEBUG2,
    PRINT_ALL,
} dbg_level;

#define FWCRITICAL(MSG) PRINT(PRINT_CRITICAL, MSG)
#define FWERROR(MSG) PRINT(PRINT_ERROR, MSG)
#define FWWARN(MSG) PRINT(PRINT_WARNING, MSG)
#define FWINFO(MSG) PRINT(PRINT_INFO, MSG)
#define FWDEBUG(MSG) PRINT(PRINT_DEBUG, MSG)
#define FWDEBUG2(MSG) PRINT(PRINT_DEBUG2, MSG)

#define FWDUMP(D, L) DUMP(PRINT_DEBUG, D, L)

#define FW_UPDATE_DEBUG 1

#ifdef FW_UPDATE_DEBUG

extern dbg_level fw_update_get_dbg_level(void);
extern void fw_update_set_dbg_level(dbg_level l);

#define PRINT(LEVEL, MSG)                                                      \
    do                                                                         \
    {                                                                          \
        if ((LEVEL) <= fw_update_get_dbg_level())                              \
        {                                                                      \
            std::stringstream ss;                                              \
            ss << '<' << LEVEL << '>' << __FUNCTION__ << ":" << __LINE__       \
               << ": " << MSG;                                                 \
            std::cerr << ss.str() << std::endl;                                \
        }                                                                      \
    } while (0)

void _dump(dbg_level lvl, const char* fn, int lineno, const char* bname,
           const gsl::span<const uint8_t>& buf);

void _dump(dbg_level lvl, const char* fn, int lineno, const char* bname,
           const void* buf, size_t len);

#define DUMP(LEVEL, BUF, ...)                                                  \
    do                                                                         \
    {                                                                          \
        if ((LEVEL) <= fw_update_get_dbg_level())                              \
        {                                                                      \
            _dump(LEVEL, __FUNCTION__, __LINE__, #BUF, BUF, ##__VA_ARGS__);    \
        }                                                                      \
    } while (0)

#else /* !FW_UPDATE_DEBUG */

#define PRINT(...)
#define DUMP(...)

#endif /* FW_UPDATE_DEBUG */

#endif /* __FW_UPDATE_DEBUG_H__ */


#pragma once
#include "iparamlist.h"
#include "optioninfo.h"
#include "configaccess.h"
#include "format.h"
#include "streamreader.h"
#include <sfun/string_utils.h>
#include <cmdlime/errors.h>
#include <cmdlime/customnames.h>
#include <gsl/gsl>
#include <vector>
#include <sstream>
#include <functional>
#include <memory>

namespace cmdlime::detail{
namespace str = sfun::string_utils;

template <typename T>
class ParamList : public IParamList{
public:
    ParamList(std::string name,
              std::string shortName,
              std::string type,
              std::function<std::vector<T>&()> paramListGetter)
        : info_(std::move(name), std::move(shortName), std::move(type))
        , paramListGetter_(std::move(paramListGetter))
    {
    }

    void setDefaultValue(const std::vector<T>& value)
    {
        hasValue_ = true;
        defaultValue_ = value;
    }

    OptionInfo& info() override
    {
        return info_;
    }

    const OptionInfo& info() const override
    {
        return info_;
    }

private:
    bool read(const std::string& data) override
    {
        if (!isDefaultValueOverwritten_){
            paramListGetter_().clear();
            isDefaultValueOverwritten_ = true;
        }

        const auto dataParts = str::split(data, ",");
        for (const auto& part : dataParts){
            auto stream = std::stringstream{part};
            paramListGetter_().emplace_back();
            if (!readFromStream(stream, paramListGetter_().back()))
                return false;
        }
        hasValue_ = true;
        return true;
    }

    bool hasValue() const override
    {
        return hasValue_;
    }

    bool isOptional() const override
    {
        return defaultValue_.has_value();
    }

    std::string defaultValue() const override
    {
        if (!defaultValue_.has_value())
            return {};
        auto stream = std::stringstream{};
        stream << "{";
        auto firstVal = true;
        for (auto& val : defaultValue_.value()){
            if (firstVal)
                stream << val;
            else
                stream << ", " << val;
            firstVal = false;
        }
        stream << "}";
        return stream.str();
    }

private:
    OptionInfo info_;
    std::function<std::vector<T>&()> paramListGetter_;
    bool hasValue_ = false;
    std::optional<std::vector<T>> defaultValue_;
    bool isDefaultValueOverwritten_ = false;
};

template <>
inline bool ParamList<std::string>::read(const std::string& data)
{
    const auto dataParts = str::split(data, ",");
    for (const auto& part : dataParts){
        paramListGetter_().push_back(part);
    }
    hasValue_ = true;
    return true;
}

template<typename T, typename TConfig>
class ParamListCreator{
    using NameProvider = typename Format<ConfigAccess<TConfig>::format()>::nameProvider;

public:
    ParamListCreator(TConfig& cfg,
                   const std::string& varName,
                   const std::string& type,
                   std::function<std::vector<T>&()> paramListGetter)
        : cfg_(cfg)
    {
        Expects(!varName.empty());
        Expects(!type.empty());
        paramList_ = std::make_unique<ParamList<T>>(NameProvider::name(varName),
                                                    NameProvider::shortName(varName),
                                                    NameProvider::valueName(type),
                                                    std::move(paramListGetter));
    }

    ParamListCreator<T, TConfig>& operator<<(const std::string& info)
    {
        paramList_->info().addDescription(info);
        return *this;
    }

    ParamListCreator<T, TConfig>& operator<<(const Name& customName)
    {
        paramList_->info().resetName(customName.value());
        return *this;
    }

    ParamListCreator<T, TConfig>& operator<<(const ShortName& customName)
    {
        static_assert(Format<ConfigAccess<TConfig>::format()>::shortNamesEnabled,
                      "Current command line format doesn't support short names");
        paramList_->info().resetShortName(customName.value());
        return *this;
    }

    ParamListCreator<T, TConfig>& operator<<(const WithoutShortName&)
    {
        static_assert(Format<ConfigAccess<TConfig>::format()>::shortNamesEnabled,
                      "Current command line format doesn't support short names");
        paramList_->info().resetShortName({});
        return *this;
    }

    ParamListCreator<T, TConfig>& operator<<(const ValueName& valueName)
    {
        paramList_->info().resetValueName(valueName.value());
        return *this;
    }

    ParamListCreator<T, TConfig>& operator()(std::vector<T> defaultValue = {})
    {
        defaultValue_ = std::move(defaultValue);
        paramList_->setDefaultValue(defaultValue_);
        return *this;
    }

    operator std::vector<T>()
    {
        ConfigAccess<TConfig>{cfg_}.addParamList(std::move(paramList_));
        return defaultValue_;
    }

private:
    std::unique_ptr<ParamList<T>> paramList_;
    std::vector<T> defaultValue_;
    TConfig& cfg_;
};

template <typename T, typename TConfig>
ParamListCreator<T, TConfig> makeParamListCreator(TConfig& cfg,
                                                  const std::string& varName,
                                                  const std::string& type,
                                                  std::function<std::vector<T>&()> paramListGetter)
{
    return ParamListCreator<T, TConfig>{cfg, varName, type, std::move(paramListGetter)};
}

}