/* Copyright (c) 2015-2016, Stanislaw Halik <sthalik@misaki.pl>

 * Permission to use, copy, modify, and/or distribute this
 * software for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice and this permission
 * notice appear in all copies.
 */

#pragma once

#include "export.hpp"

#include "bundle.hpp"
#include "slider.hpp"
#include "base-value.hpp"
#include "value-traits.hpp"
#include "compat/macros.hpp"

#include <cstdio>
#include <type_traits>
#include <typeinfo>
#include <typeindex>
#include <utility>

#include <QVariant>
#include <QString>
#include <QPointF>
#include <QList>
#include <QMutex>

namespace options {

template<typename t>
class value final : public value_
{
    using traits = detail::value_traits<t, t, void>;
    using stored_type = typename traits::stored_type;

    static bool is_equal(const QVariant& val1, const QVariant& val2)
    {
        return val1.value<stored_type>() == val2.value<stored_type>();
    }

    cc_noinline
    t get() const
    {
        if (self_name.isEmpty())
            return def;

        QVariant variant = b->get<QVariant>(self_name);

        if (!b->contains(self_name) || variant.type() == QVariant::Invalid)
            return def;

        const stored_type x(variant.value<stored_type>());

        return traits::from_value(traits::from_storage(x), def);
    }

public:
    cc_noinline
    t operator=(const t& datum)
    {
        if (self_name.isEmpty())
            return def;

        if (datum != get())
            store(traits::to_storage(datum));

        return datum;
    }

    static constexpr inline Qt::ConnectionType DIRECT_CONNTYPE = Qt::DirectConnection;
    static constexpr inline Qt::ConnectionType SAFE_CONNTYPE = Qt::QueuedConnection;

    cc_noinline
    value(bundle b, const QString& name, t def) :
        value_(b, name, &is_equal, std::type_index(typeid(stored_type))),
        def(def)
    {
        if (!self_name.isEmpty())
            QObject::connect(b.get(), SIGNAL(reloading()),
                             this, SLOT(reload()),
                             DIRECT_CONNTYPE);
    }

    cc_noinline
    t default_value() const
    {
        return def;
    }

    cc_noinline
    void set_to_default() override
    {
        *this = def;
    }

    operator t() const { return get(); }

    t operator->() const
    {
        return get();
    }

    cc_noinline
    void reload() override
    {
        if (!self_name.isEmpty())
            *this = static_cast<t>(*this);
    }

    cc_noinline
    void bundle_value_changed() const override
    {
        if (!self_name.isEmpty())
            emit valueChanged(traits::to_storage(get()));
    }

    t operator()() const
    {
        return get();
    }

    t operator*() const
    {
        return get();
    }

    template<typename u>
    u to() const
    {
        return static_cast<u>(get());
    }

private:
    const t def;
};

#if !defined OTR_INST_VALUE
#   define OTR_INST_VALUE OTR_TEMPLATE_IMPORT
#endif

#if !defined __APPLE__
    OTR_INST_VALUE(value<double>);
    OTR_INST_VALUE(value<float>);
    OTR_INST_VALUE(value<int>);
    OTR_INST_VALUE(value<bool>);
    OTR_INST_VALUE(value<QString>);
    OTR_INST_VALUE(value<slider_value>);
    OTR_INST_VALUE(value<QPointF>);
    OTR_INST_VALUE(value<QVariant>);
    OTR_INST_VALUE(value<QList<double>>);
    OTR_INST_VALUE(value<QList<float>>);
    OTR_INST_VALUE(value<QList<int>>);
    OTR_INST_VALUE(value<QList<bool>>);
    OTR_INST_VALUE(value<QList<QString>>);
    OTR_INST_VALUE(value<QList<slider_value>>);
    OTR_INST_VALUE(value<QList<QPointF>>);
    OTR_INST_VALUE(value<QList<QVariant>>);
#endif

} // ns options