//  supercollider-style synthdef
//  Copyright (C) 2008-2012 Tim Blechmann
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; see the file COPYING.  If not, write to
//  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
//  Boston, MA 02111-1307, USA.

#pragma once

#include <cstdint>
#include <map>
#include <string>
#include <vector>
#include <filesystem>

#include <boost/align/aligned_allocator.hpp>

#include "utilities/named_hash_entry.hpp"

#include "SC_Types.h"
#include "SC_Wire.h"

namespace nova {

class sc_synthdef {
    typedef std::vector<float, boost::alignment::aligned_allocator<float, 64>> float_vector;
    typedef std::vector<char, boost::alignment::aligned_allocator<char, 64>> char_vector;
    typedef std::map<symbol, int32_t, std::less<symbol>> parameter_index_map_t;

public:
    struct input_spec {
        input_spec(int32_t source, int32_t index): source(source), index(index) {}

        bool operator<(input_spec const& rhs) const {
            if (source < rhs.source)
                return true;
            if (source > rhs.source)
                return false;
            return index < rhs.index;
        }

        int32_t source; /* index of ugen or -1 for constant */
        int32_t index; /* number of output or constant index */
    };

    typedef std::vector<input_spec, boost::alignment::aligned_allocator<input_spec, 64>> input_spec_vector;

    struct unit_spec_t {
        explicit unit_spec_t(const char*& buffer, const char* buffer_end, int version);

        unit_spec_t(symbol const& name, int16_t rate, int16_t special_index, input_spec_vector const& in_specs,
                    char_vector const& out_specs):
            name(name),
            rate(rate),
            special_index(special_index),
            input_specs(in_specs),
            output_specs(out_specs) {}

        unit_spec_t(unit_spec_t&& rhs) = default;
        unit_spec_t(unit_spec_t const& rhs) = default;
        unit_spec_t& operator=(unit_spec_t const& rhs) = default;

        symbol name;
        int16_t rate; /* 0: scalar rate, 1: buffer rate, 2: full rate, 3: demand rate */
        int16_t special_index;

        input_spec_vector input_specs;
        char_vector output_specs; /* calculation rates */
        std::vector<int32_t, boost::alignment::aligned_allocator<int32_t, 64>> buffer_mapping;

        std::size_t memory_requirement(void) const {
            return input_specs.size() * (sizeof(Wire*) + sizeof(float*))
                + output_specs.size() * (sizeof(Wire*) + sizeof(float*)) + output_specs.size() * sizeof(Wire);
        }

        class sc_ugen_def* prototype;
    };

    friend class sc_synth_definition;
    friend class sc_synth;
    friend class sc_ugen_factory;
    friend class sc_ugen_def;

    typedef std::vector<unit_spec_t, boost::alignment::aligned_allocator<unit_spec_t, 64>> graph_t;
    typedef std::vector<int32_t, boost::alignment::aligned_allocator<int32_t, 64>> calc_units_t;

    sc_synthdef(const char*& buffer, const char* buffer_end, int version);

    sc_synthdef(sc_synthdef&& rhs) = default;
    sc_synthdef(sc_synthdef const& rhs) = default;
    sc_synthdef& operator=(sc_synthdef const& rhs) = default;

    std::string dump(void) const;

    symbol const& name(void) const { return name_; }

    std::size_t parameter_count(void) const { return parameters.size(); }

    std::size_t unit_count(void) const { return graph.size(); }

    std::size_t calc_unit_count(void) const { return calc_unit_indices.size(); }

    std::size_t memory_requirement(void) const {
        assert(memory_requirement_);
        return memory_requirement_;
    }

private:
    void read_synthdef(const char*& data, const char* end, int version);

    /** assign buffers, collect memory requirement & cache ugen prototype */
    void prepare(void);

    symbol name_;
    float_vector constants;
    float_vector parameters;
    parameter_index_map_t parameter_map;

    graph_t graph;
    unsigned int buffer_count;
    calc_units_t calc_unit_indices; /**< indices of the units, that need to be calculated */
    std::size_t memory_requirement_;
};

std::vector<sc_synthdef> read_synthdefs(const char* buffer, const char* buffer_end);
std::vector<sc_synthdef> read_synthdef_file(std::filesystem::path const& filename);

} /* namespace nova */
