/****************************************************************************
**                                MIT License
**
** Copyright (C) 2021 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
** Author: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
**
** This file is part of KDToolBox (https://github.com/KDAB/KDToolBox).
**
** 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 (including the next paragraph)
** 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.
****************************************************************************/

// SPDX-License-Identifier: MIT

#ifndef KDTOOLBOX_STLCONTAINERADAPTOR_H
#define KDTOOLBOX_STLCONTAINERADAPTOR_H
#pragma once

#include <vector>
#include <algorithm>
#include <iterator>

namespace KDToolBox {
namespace StlContainerAdaptor {

template<typename T, typename... Args>
struct StdVectorAdaptor : std::vector<T, Args...>
{
    using base_container = std::vector<T, Args...>;
    using base_size_type = typename base_container::size_type;
    using size_type = int;
    using value_type = typename base_container::value_type;
    // using ConstIterator = typename base_container::const_iterator;

    // Construction / RO5
    using base_container::base_container;

    StdVectorAdaptor() = default; // work around broken compilers in C++17 mode
    explicit StdVectorAdaptor(size_type count)
        : base_container(base_size_type(count))
    {
    }
    StdVectorAdaptor(size_type count, const value_type &v)
        : base_container(base_size_type(count), v)
    {
    }

    // It's a lot of churn to fix all cases
    //[[deprecated("Use clone() instead; copies of non-Qt containers are not cheap")]]
    StdVectorAdaptor(const StdVectorAdaptor &) = default;
    //[[deprecated("Use assignFrom() instead; copies of non-Qt containers are not cheap")]]
    StdVectorAdaptor &operator=(const StdVectorAdaptor &) = default;

    StdVectorAdaptor(StdVectorAdaptor &&) = default;
    StdVectorAdaptor &operator=(StdVectorAdaptor &&) = default;

    ~StdVectorAdaptor() = default;

    StdVectorAdaptor clone() const
    {
        return *this;
    }

    StdVectorAdaptor &assignFrom(const StdVectorAdaptor &other)
    {
        return *this = other;
    }
    StdVectorAdaptor &assignFrom(StdVectorAdaptor &&other)
    {
        return *this = std::move(other);
    }

    // Iterators
    decltype(auto) constBegin() const
    {
        return this->cbegin();
    }
    decltype(auto) constEnd() const
    {
        return this->cend();
    }

    // Data access
    decltype(auto) constData() const
    {
        return this->data();
    }
    decltype(auto) at(size_type i) const
    {
        return base_container::operator[](base_size_type(i));
    }
    decltype(auto) operator[](size_type i)
    {
        return base_container::operator[](base_size_type(i));
    }
    decltype(auto) operator[](size_type i) const
    {
        return base_container::operator[](base_size_type(i));
    }

    auto value(size_type i, const T &defaultValue) const
    {
        if (i >= 0 && i < size())
            return at(i);
        return defaultValue;
    }
    auto value(size_type i) const
    {
        if (i >= 0 && i < size())
            return at(i);
        return value_type();
    }

    decltype(auto) first()
    {
        return this->front();
    }
    decltype(auto) first() const
    {
        return this->front();
    }
    decltype(auto) constFirst() const
    {
        return this->front();
    }

    decltype(auto) last()
    {
        return this->back();
    }
    decltype(auto) last() const
    {
        return this->back();
    }
    decltype(auto) constLast() const
    {
        return this->back();
    }

    // Size and capacity
    decltype(auto) isEmpty() const
    {
        return this->empty();
    }
    decltype(auto) size() const
    {
        return size_type(base_container::size());
    }
    decltype(auto) count() const
    {
        return size();
    }
    decltype(auto) length() const
    {
        return size();
    }
    decltype(auto) capacity() const
    {
        return size_type(base_container::capacity());
    }
    void reserve(size_type s)
    {
        base_container::reserve(base_size_type(s));
    }
    void squeeze()
    {
        this->shrink_to_fit();
    }


    // Insertion
    void append(const value_type &v)
    {
        this->push_back(v);
    }
    void append(value_type &&v)
    {
        this->push_back(std::move(v));
    }
    void append(const StdVectorAdaptor &other)
    {
        if (this != &other) {
            this->insert(this->end(), other.begin(), other.end());
        } else {
            this->reserve(2 * size());
            std::copy(this->begin(), this->end(), std::back_inserter(*this));
        }
    }
    void prepend(const value_type &v)
    {
        this->insert(this->begin(), v);
    }
    void prepend(value_type &&v)
    {
        this->insert(this->begin(), std::move(v));
    }

    using base_container::insert;
    decltype(auto) insert(size_type position, const value_type &v)
    {
        return this->insert(this->begin() + position, v);
    }
    decltype(auto) insert(size_type position, value_type &&v)
    {
        return this->insert(this->begin() + position, std::move(v));
    }


    // Removal
    void removeFirst()
    {
        this->erase(this->begin());
    }
    void removeLast()
    {
        this->pop_back();
    }
    void remove(size_type position)
    {
        this->erase(this->begin() + position);
    }
    void remove(size_type position, size_type count)
    {
        const auto b = this->begin();
        this->erase(b + position, b + position + count);
    }
    void removeAt(size_type position)
    {
        this->erase(this->begin() + position);
    }

    template<typename AT>
    decltype(auto) removeAll(const AT &v)
    {
        const auto b = this->begin();
        const auto e = this->end();
        const auto i = std::remove(b, e, v);
        const auto result = size_type(e - i);
        this->erase(i, e);
        return result;
    }

    template<typename AT>
    bool removeOne(const AT &v)
    {
        const auto b = this->begin();
        const auto e = this->end();
        const auto i = std::find(b, e, v);
        if (i == e)
            return false;
        this->erase(i);
        return true;
    }

    decltype(auto) takeAt(size_type i)
    {
        const auto it = this->begin() + i;
        const auto result = std::move(*it);
        this->erase(it);
        return result;
    }
    decltype(auto) takeLast()
    {
        return takeAt(size() - 1);
    }
    decltype(auto) takeFirst()
    {
        return takeAt(0);
    }


    // Search
    template<typename AT>
    bool contains(const AT &v) const
    {
        return indexOf(v) >= 0;
    }

    template<typename AT>
    size_type indexOf(const AT &v, size_type from = 0) const
    {
        const auto s = size();
        if (from < 0)
            from = (std::max)(from + s, 0);
        if (from < s) {
            const auto b = this->begin();
            const auto e = this->end();
            const auto i = std::find(b + from, e, v);
            if (i != e)
                return size_type(i - b);
        }
        return size_type(-1);
    }

    template<typename AT>
    size_type lastIndexOf(const AT &v, size_type from = -1) const
    {
        const auto s = size();
        if (from < 0)
            from += s;
        else if (from >= s)
            from = s - 1;

        if (from >= 0) {
            const auto b = this->begin();

            const auto revB = std::make_reverse_iterator(b + from + 1);
            const auto revE = std::make_reverse_iterator(b);
            const auto i = std::find(revB, revE, v);
            if (i != revE)
                return size_type(i.base() - b) - 1;
        }
        return size_type(-1);
    }

    template<typename AT>
    bool startsWith(const AT &v) const
    {
        return this->front() == v;
    }

    template<typename AT>
    bool endsWith(const AT &v) const
    {
        return this->back() == v;
    }


    // Miscellanea
    StdVectorAdaptor &fill(const value_type &v, size_type i = -1)
    {
        if (i < 0)
            i = size();
        this->assign(base_size_type(i), v);
        return *this;
    }

    StdVectorAdaptor mid(size_type pos, size_type len = -1)
    {
        const auto s = size();

        if (len < 0)
            len = s;
        len = (std::min)(len, s - pos);

        const auto b = this->begin() + pos;
        const auto e = b + len;
        return StdVectorAdaptor(b, e);
    }

    void move(size_type from, size_type to)
    {
        const auto b = this->begin();
        if (from < to)
            std::rotate(b + from, b + from + 1, b + to + 1);
        else
            std::rotate(b + to, b + from, b + from + 1);
    }

    void replace(size_type pos, const value_type &v)
    {
        at(pos) = v;
    }

    void swapItemsAt(size_type i, size_type j)
    {
        qSwap(at(i), at(j));
    }


    // Misc operators
    template<typename AT>
    decltype(auto) operator<<(const typename StdVectorAdaptor<AT>::value_type &v)
    {
        push_back(v);
        return *this;
    }
    template<typename AT>
    decltype(auto) operator<<(typename StdVectorAdaptor<AT>::value_type &&v)
    {
        push_back(std::move(v));
        return *this;
    }

    template<typename AT>
    decltype(auto) operator<<(const StdVectorAdaptor<AT> &rhs)
    {
        append(rhs);
        return *this;
    }

    template<typename AT>
    decltype(auto) operator+(const StdVectorAdaptor<AT> &rhs)
    {
        StdVectorAdaptor<AT> result;
        result.reserve(size() + rhs.size());
        result.insert(result.end(), base_container::begin(), base_container::end());
        result.insert(result.end(), rhs.begin(), rhs.end());
        return result;
    }

    decltype(auto) operator+=(const StdVectorAdaptor &other)
    {
        append(other);
        return *this;
    }
};

} // namespace StlContainerAdaptor
} // namespace KDToolBox

namespace KDDockWidgets {

template<typename T, typename... Args>
using Vector = KDToolBox::StlContainerAdaptor::StdVectorAdaptor<T, Args...>;

}

#endif // KDTOOLBOX_STLCONTAINERADAPTOR_H
