/*
    SPDX-FileCopyrightText: 2018 Valentin Boettcher <valentin@boettcher.cf>

    SPDX-License-Identifier: GPL-2.0-or-later
*/

#pragma once

#include <QDataStream>
#include <QDebug>

#include "listcomponent.h"
#include "binarylistcomponent.h"
#include "auxiliary/kspaths.h"

//TODO: Error Handling - SERIOUSLY

/**
 * @class BinaryListComponent
 * @short provides functionality for loading the component data from Binary
 * @author Valentin Boettcher
 * @version 1.0
 *
 * This class is an abstract Template which requires that the type `T` is some child of
 * `SkyObject` and the type `Component` is some child of `ListComponent`. The class `T`
 * must provide a static `TYPE` property of the type `SkyObject::TYPE`. This is required
 * because access to the `type()` method is inconvenient here!
 *
 * The derived class must provide a `void loadFromText()` method, which loads the component
 * via `addListObject` or similar. (This method implements parsing etc, and cannot be
 * abstracted by this class.)
 *
 * Finally, one has to add this template as a friend class upon deriving it.
 * This is a concession to the already present architecture.
 *
 * File paths are determent by the means of KSPaths::writableLocation.
 */
template <class T, typename Component>
class BinaryListComponent
{
    public:
        /**
         * @brief BinaryListComponent
         * @param parent a reference to the inheriting child
         * @param basename the base filename for the binary
         *
         * Sets the file extensions to `dat` for text and `bin` for binary.
         */
        BinaryListComponent(Component* parent, QString basename);

        /**
         * @brief BinaryListComponent
         * @param parent a reference to the inheriting child
         * @param basename the base filename for the binary
         * @param txtExt text data file extension
         * @param binExt binary data file extension
         */
        BinaryListComponent(Component* parent, QString basename, QString txtExt, QString binExt);

        /**
         * @brief Virtual destructor for proper cleanup of derived classes
         */
        virtual ~BinaryListComponent() = default;

    protected:
        /**
         * @brief loadData
         * @short Calls `loadData(false)`
         */
        virtual void loadData();

        /**
         * @brief loadData
         * @short Load the component data from binary (if available) or from text
         * @param dropBinaryFile whether to drop the current binary (and to recreate it)
         *
         * Tip: If you want to reload the data and recreate the binfile, just call
         * loadData(true).
         */
        virtual void loadData(bool dropBinaryFile);

        /**
         * @brief loadDataFromBinary
         * @short Opens the default binfile and calls `loadDataFromBinary([FILE])`
         */
        virtual void loadDataFromBinary();

        /**
         * @brief loadDataFromBinary
         * @param binfile the binary file
         * @short Loads the component data from the given binary.
         */
        virtual void loadDataFromBinary(QFile &binfile);

        /**
         * @brief writeBinary
         * @short Opens the default binfile and calls `writeBinary([FILE])`
         */
        virtual void writeBinary();

        /**
         * @brief writeBinary
         * @param binfile
         * @short Writes the component data to the specified binary. (Destructive)
         */
        virtual void writeBinary(QFile &binfile);

        /**
         * @brief loadDataFromText
         * @short Load the component data from text.
         *
         * This method shall be implemented by those who derive this class.
         *
         * This method should load the component data from text by the use of
         * `addListObject`  or similar.
         */
        virtual void loadDataFromText() = 0;

        // TODO: Rename, and integrate it into a wrapper
        //virtual void updateDataFile() = 0; // legacy from current implementation!

        /**
         * @brief dropBinary
         * @short Removes the binary file.
         * @return True if operation succeeds
         */
        virtual bool dropBinary();

        /**
         * @brief dropText
         * @short Removes the text file.
         * @return True if operation succeeds
         */
        virtual bool dropText();

        /**
         * @brief clearData
         * @short Removes the current component data where necessary.
         */
        virtual void clearData();

        QString filepath_txt;
        QString filepath_bin;

        // Don't allow the children to mess with the Binary Version!
    private:
        QDataStream::Version binversion = QDataStream::Qt_5_5;
        Component* parent;
};

template<class T, typename Component>
BinaryListComponent<T, Component>::BinaryListComponent(Component *parent,
        QString basename) :  BinaryListComponent<T, Component>(parent, basename, "dat", "bin") {}

template<class T, typename Component>
BinaryListComponent<T, Component>::BinaryListComponent(Component *parent, QString basename, QString txtExt,
        QString binExt) : parent { parent }
{
    filepath_bin = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(basename + '.' + binExt);
    filepath_txt = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(basename + '.' + txtExt);
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::loadData()
{
    loadData(false);
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::loadData(bool dropBinaryFile)
{
    // Clear old Stuff (in case of reload)
    clearData();

    // Drop Binary file for a fresh reload
    if(dropBinaryFile)
        dropBinary();

    QFile binfile(filepath_bin);
    if (binfile.exists())
    {
        loadDataFromBinary(binfile);
    }
    else
    {
        loadDataFromText();
        writeBinary(binfile);
    }
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::loadDataFromBinary()
{
    QFile binfile(filepath_bin);
    loadDataFromBinary(binfile);
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::loadDataFromBinary(QFile &binfile)
{
    // Open our binary file and create a Stream
    if (binfile.open(QIODevice::ReadOnly))
    {
        QDataStream in(&binfile);

        // Use the specified binary version
        // TODO: Place this into the config
        in.setVersion(binversion);
        in.setFloatingPointPrecision(QDataStream::DoublePrecision);

        while(!in.atEnd())
        {
            T *new_object = nullptr;
            in >> new_object;

            parent->appendListObject(new_object);
            // Add name to the list of object names
            parent->objectNames(T::TYPE).append(new_object->name());
            parent->objectLists(T::TYPE).append(QPair<QString, const SkyObject *>(new_object->name(), new_object));
        }
        binfile.close();
    }
    else qWarning() << "Failed loading binary data from" << binfile.fileName();
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::writeBinary()
{
    QFile binfile(filepath_bin);
    writeBinary(binfile);
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::writeBinary(QFile &binfile)
{
    // Open our file and create a stream
    binfile.open(QIODevice::WriteOnly | QIODevice::Truncate);
    QDataStream out(&binfile);
    out.setVersion(binversion);
    out.setFloatingPointPrecision(QDataStream::DoublePrecision);

    // Now just dump out everything
    for(auto object : parent->m_ObjectList)
    {
        out << *((T*)object);
    }

    binfile.close();
}

template<class T, typename Component>
bool  BinaryListComponent<T, Component>::dropBinary()
{
    return QFile::remove(filepath_bin);
}

template<class T, typename Component>
bool  BinaryListComponent<T, Component>::dropText()
{
    return QFile::remove(filepath_txt);
}

template<class T, typename Component>
void  BinaryListComponent<T, Component>::clearData()
{
    // Clear lists
    qDeleteAll(parent->m_ObjectList);
    parent->m_ObjectList.clear();
    parent->m_ObjectHash.clear();

    parent->objectLists(T::TYPE).clear();
    parent->objectNames(T::TYPE).clear();
}
