/*
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/.
*/

#pragma once

#ifdef _DEBUG
#undef _DEBUG // bez tego macra Py_DECREF powoduja problemy przy linkowaniu
#include "Python.h"
#define _DEBUG
#else
#include "Python.h"
#endif
#include "Classes.h"

#define PyGetFloat(param) PyFloat_FromDouble(param)
#define PyGetInt(param) PyInt_FromLong(param)
#define PyGetBool(param) param ? Py_True : Py_False
#define PyGetString(param) PyString_FromString(param)

// TODO: extract common base and inherit specialization from it
class render_task {

public:
// constructors
    render_task( PyObject *Renderer, dictionary_source *Input, GLuint Target ) :
        m_renderer( Renderer ), m_input( Input ), m_target( Target )
    {}
// methods
    void run();
    void cancel();
    auto target() const -> texture_handle { return m_target; }

private:
// members
    PyObject *m_renderer {nullptr};
    dictionary_source *m_input { nullptr };
    GLuint m_target { 0 };
};



class python_taskqueue {

public:
// types
    struct task_request {

        std::string const &renderer;
        dictionary_source *input;
        GLuint target;
    };
// constructors
    python_taskqueue() = default;
// methods
    // initializes the module. returns true on success
    auto init() -> bool;
    // shuts down the module
    void exit();
    // adds specified task along with provided collection of data to the work queue. returns true on success
    auto insert( task_request const &Task ) -> bool;
    // executes python script stored in specified file. returns true on success
    auto run_file( std::string const &File, std::string const &Path = "" ) -> bool;
    // acquires the python gil and sets the main thread as current
    void acquire_lock();
    // releases the python gil and swaps the main thread out
    void release_lock();

private:
// types
    static int const WORKERCOUNT { 1 };
    using worker_array = std::array<std::thread, WORKERCOUNT >;
    using rendertask_sequence = threading::lockable< std::deque<render_task *> >;
// methods
    auto fetch_renderer( std::string const Renderer ) -> PyObject *;
    void run( GLFWwindow *Context, rendertask_sequence &Tasks, threading::condition_variable &Condition, std::atomic<bool> &Exit );
    void error();
// members
    PyObject *m_main { nullptr };
    PyObject *m_stderr { nullptr };
    PyThreadState *m_mainthread{ nullptr };
    worker_array m_workers;
    threading::condition_variable m_condition; // wakes up the workers
    std::atomic<bool> m_exit { false }; // signals the workers to quit
    std::unordered_map<std::string, PyObject *> m_renderers; // cache of python classes
    rendertask_sequence m_tasks;
};
