/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2018-2019 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../std.hpp"
#include "../debug/panic.hpp"
#include "../typeidx.hpp"

#include <cstddef>
#include <type_traits>
#include <functional>
#include <utility>


namespace cruft::types {
    ///////////////////////////////////////////////////////////////////////////
    /// Computes the number of elements of a scalar (ie, 1), or of  statically
    /// sized vector type
    template <typename T, typename = void>
    struct arity_trait { };


    //-------------------------------------------------------------------------
    // void arity is explicitly zero.
    template <>
    struct arity_trait<void, void> : public std::integer_sequence<std::size_t, 0> { };


    //-------------------------------------------------------------------------
    // All basic types are unit arity (except for void which is zero).
    template <typename T>
    struct arity_trait<
        T,
        std::enable_if_t<std::is_fundamental_v<T>>
    > : public std::integral_constant<
        std::size_t, 1u
    > { };


    //-------------------------------------------------------------------------
    // Punt the handling of any enum to the underlying type.
    template <typename EnumT>
    struct arity_trait<
        EnumT,
        std::enable_if_t<std::is_enum_v<EnumT>>
    > : public arity_trait<
        std::underlying_type_t<EnumT>
    > { };


    //-------------------------------------------------------------------------
    template <std::size_t S, typename T>
    struct arity_trait<
        std::array<T,S>
    > : public std::integral_constant<std::size_t, S> {  };


    //-------------------------------------------------------------------------
    template <typename T>
    constexpr auto arity_v = arity_trait<T>::value;


    ///////////////////////////////////////////////////////////////////////////
    enum class category {
        NONE,
        INTEGER,
        REAL,
        BOOL,
        ENUM,
    };


    ///------------------------------------------------------------------------
    /// A description of the type that makes up a scalar or vector type.
    ///
    /// A vector3f would be { REAL, sizeof(float), 3 };
    ///
    /// DO NOT add a 'size' member function as it tends to be used in a way
    /// that confuses total byte size, element byte size, and arity.
    /// Instead: use the bytes member function, or the arity member variable.
    struct description {
        /// The fundamental nature of the type; is it integral, real, bool, etc.
        enum category category;

        /// Is the type considered to be signed?
        ///
        /// For some types, like void, this does not make sense. In this case
        /// the value must be false.
        ///
        /// For other types this value will almost certainly be one value in a
        /// realistic system; eg, signed floating point numbers.
        bool signedness;

        /// The number of bytes for an individual instance of this type.
        std::size_t width;

        /// The number of variables that make up an instance.
        std::size_t arity;

        std::size_t alignment;

        /// The value from typeidx for the constructed type (if it is known
        /// and has been registered), otherwise 0.
        int index;

        /// Returns the number of bytes required to store this type.
        constexpr std::size_t
        bytes (void) const noexcept
        {
            return width * arity;
        }
    };


    //-------------------------------------------------------------------------
    constexpr bool
    operator== (description const &a, description const &b) noexcept
    {
        return a.category   == b.category   &&
               a.signedness == b.signedness &&
               a.width      == b.width      &&
               a.arity      == b.arity      &&
               a.alignment  == b.alignment  &&
               (a.index == 0 || b.index == 0 || a.index == b.index);
    }


    //-------------------------------------------------------------------------
    constexpr bool
    operator!= (description const &a, description const &b) noexcept
    {
        return !(a == b);
    }


    /// A comparator that compares values as equal if they are an enum and the
    /// underlying_type of said enum.
    ///
    /// This is distinct from the raw equality operator so as to not pessimise
    /// the usability of the equality operator (for tasks that required strict
    /// equality).
    struct underlying_comparator {
        bool operator() (
            description const &a,
            description const &b
        ) const noexcept {
            // If one of the descriptions is ENUM then both need to be either ENUM
            // or INTEGER (because we try to compare enums and their underlying
            // types as equal).
            if (a.category == category::ENUM || b.category == category::ENUM) {
                if (a.category != category::ENUM && a.category != category::INTEGER)
                    return false;
                if (b.category != category::ENUM && b.category != category::INTEGER)
                    return false;

                // Indices only need to match if they're both an ENUM,
                // otherwise we're testing the underlying type.
                if (a.category == category::ENUM && b.category == category::ENUM)
                    if (a.index != b.index)
                        return false;
            } else {
                if (a.index != b.index)
                    return false;
            }

            // All details other must be identical;
            return a.signedness == b.signedness &&
                   a.width      == b.width      &&
                   a.arity      == b.arity      &&
                   a.alignment  == b.alignment;
        };
    };


    //-------------------------------------------------------------------------
    template <typename T, typename = void>
    struct category_traits;

    template <> struct category_traits<u08> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<u16> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<u32> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<u64> : public std::integral_constant<category,category::INTEGER> {};

    template <> struct category_traits<i08> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<i16> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<i32> : public std::integral_constant<category,category::INTEGER> {};
    template <> struct category_traits<i64> : public std::integral_constant<category,category::INTEGER> {};

    template <> struct category_traits<f32> : public std::integral_constant<category,category::REAL> {};
    template <> struct category_traits<f64> : public std::integral_constant<category,category::REAL> {};

    template <> struct category_traits<bool> : public std::integral_constant<category,category::BOOL> {};


    template <typename EnumT>
    struct category_traits<
        EnumT,
        std::enable_if_t<
            std::is_enum_v<EnumT>
        >
    > : public std::integral_constant<category,category::ENUM>
    { };


    template <typename T>
    constexpr auto category_traits_v = category_traits<T>::value;


    //-------------------------------------------------------------------------
    template <typename, typename = void>
    struct signedness_traits;

    template <> struct signedness_traits<u08> : public std::false_type {};
    template <> struct signedness_traits<u16> : public std::false_type {};
    template <> struct signedness_traits<u32> : public std::false_type {};
    template <> struct signedness_traits<u64> : public std::false_type {};

    template <> struct signedness_traits<i08> : public std::true_type {};
    template <> struct signedness_traits<i16> : public std::true_type {};
    template <> struct signedness_traits<i32> : public std::true_type {};
    template <> struct signedness_traits<i64> : public std::true_type {};

    template <> struct signedness_traits<f32> : public std::true_type {};
    template <> struct signedness_traits<f64> : public std::true_type {};

    template <> struct signedness_traits<bool> : public std::false_type {};

    template <typename EnumT>
    struct signedness_traits<
        EnumT,
        std::enable_if_t<
            std::is_enum_v<EnumT>
        >
    >: public signedness_traits<std::underlying_type_t<EnumT>> {};

    template <typename T>
    constexpr auto signedness_traits_v = signedness_traits<T>::value;


    //-------------------------------------------------------------------------
    template <typename T>
    description
    make_description (void) noexcept
    {
        if constexpr (std::is_void_v<T>) {
            return {
                .category   = category::NONE,
                .signedness = false,
                .width      = 0,
                .arity      = 1,
                .alignment  = 0,
                .index      = cruft::typeidx<T> (),
            };
        } else {
            return {
                .category = category_traits_v<T>,
                .signedness = signedness_traits_v<T>,
                .width = sizeof (T),
                .arity = arity_v<T>,
                .alignment = alignof (T),
                .index = cruft::typeidx<T> (),
            };
        }
    }
}


#include <iosfwd>
namespace cruft::types {
    std::ostream& operator<< (std::ostream&, category);
    std::ostream& operator<< (std::ostream&, description);
}