//  synth based on supercollider-style synthdef
//  Copyright (C) 2009, 2010 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.

#include <cstdio>

#include "SC_Win32Utils.h"

#include <boost/align/align_up.hpp>

#include "sc_msg_iter.h"
#include "sc_synth.hpp"
#include "sc_ugen_factory.hpp"
#include "../server/server.hpp"

namespace nova {

sc_synth::sc_synth(int node_id, sc_synth_definition_ptr const& prototype): abstract_synth(node_id, prototype) {
    World const& world = sc_factory->world;
    const bool rt_synthesis = world.mRealTime;

    mNode.mWorld = &sc_factory->world;
    rgen.init((uint32_t)(uint64_t)this);

    /* initialize sc wrapper class */
    mRGen = &rgen;
    mSubsampleOffset = world.mSubsampleOffset;
    mSampleOffset = world.mSampleOffset;
    mLocalAudioBusUnit = nullptr;
    mLocalControlBusUnit = nullptr;

    localBufNum = 0;
    localMaxBufNum = 0;

    // so far mPrivate is only used for queued unit commands,
    // i.e. it just points to the head of the list.
    mPrivate = nullptr;

    mNode.mID = node_id;

    sc_synthdef const& synthdef = *prototype;

    const size_t parameter_count = synthdef.parameter_count();
    const size_t constants_count = synthdef.constants.size();

    /* we allocate one memory chunk */
    const size_t wire_buffer_alignment = 64 * 8; // align to cache line boundaries
    const size_t alloc_size = prototype->memory_requirement();

    const size_t sample_alloc_size =
        world.mBufLength * synthdef.buffer_count + wire_buffer_alignment /* for alignment */;

    const size_t total_alloc_size = alloc_size + sample_alloc_size * sizeof(sample);

    char* raw_chunk = rt_synthesis ? (char*)rt_pool.malloc(total_alloc_size) : (char*)malloc(total_alloc_size);

    if (raw_chunk == nullptr)
        throw std::bad_alloc();

    linear_allocator allocator(raw_chunk);

    /* prepare controls */
    mNumControls = parameter_count;
    mControls = allocator.alloc<float>(parameter_count);
    mControlRates = allocator.alloc<int>(parameter_count);
    mAudioBusOffsets = allocator.alloc<int>(parameter_count);
    mMapControls = allocator.alloc<float*>(parameter_count);

    /* initialize controls */
    for (size_t i = 0; i != parameter_count; ++i) {
        mControls[i] = synthdef.parameters[i]; /* initial parameters */
        mMapControls[i] = &mControls[i]; /* map to control values */
        mControlRates[i] = 0; /* init to 0*/
        mAudioBusOffsets[i] = -1; /* init to -1: not mapped to an audio bus yet */
    }

    /* allocate constant wires */
    mWire = allocator.alloc<Wire>(constants_count);
    for (size_t i = 0; i != synthdef.constants.size(); ++i) {
        Wire* wire = mWire + i;
        wire->mFromUnit = nullptr;
        wire->mCalcRate = 0;
        wire->mBuffer = nullptr;
        wire->mScalarValue = get_constant(i);
    }

    unit_count = prototype->unit_count();
    calc_unit_count = prototype->calc_unit_count();
    units = allocator.alloc<Unit*>(unit_count);
    calc_units = allocator.alloc<Unit*>(calc_unit_count + 1); // over-allocate to allow prefetching
    unit_buffers = allocator.alloc<sample>(sample_alloc_size);

    unit_buffers = static_cast<sample*>(boost::alignment::align_up(unit_buffers, wire_buffer_alignment));

    /* allocate unit generators */
    sc_factory->allocate_ugens(synthdef.graph.size());
    for (size_t i = 0; i != synthdef.graph.size(); ++i) {
        sc_synthdef::unit_spec_t const& spec = synthdef.graph[i];
        units[i] = spec.prototype->construct(spec, this, i, &sc_factory->world, allocator);
    }

    for (size_t i = 0; i != synthdef.calc_unit_indices.size(); ++i) {
        int32_t index = synthdef.calc_unit_indices[i];
        calc_units[i] = units[index];
    }

    assert((char*)mControls + alloc_size <= allocator.alloc<char>()); // ensure the memory boundaries
}

sc_synth::~sc_synth(void) { assert(!initialized); }

extern "C" {
void* rt_alloc(World* dummy, size_t size);
void* rt_free(World* dummy, void* ptr);
}

// FIXME: for some reason, we cannot initialize multiple synths in parallel
static spin_lock scsynth_preparation_lock;

void sc_synth::prepare(void) {
    assert(!initialized);

    scsynth_preparation_lock.lock();
    std::for_each(units, units + unit_count, [](Unit* unit) {
        sc_ugen_def* def = reinterpret_cast<sc_ugen_def*>(unit->mUnitDef);
        def->initialize(unit);
    });
    scsynth_preparation_lock.unlock();

    initialized = true;

    // *finally* dispatch queued unit commands
    auto cmd = (sc_unit_cmd*)mPrivate;
    while (cmd) {
        auto next = cmd->next;

        sc_msg_iter args(cmd->size, cmd->data);
        // error checking has already be done in Unit_DoCmd()
        int node_id = args.geti();
        assert(node_id == mNode.mID);
        int ugen_index = args.geti();
        const char* cmd_name = args.gets();

        apply_unit_cmd(cmd_name, ugen_index, &args);

        // NOTE: we can't just do rt_pool.free() because other synths might be
        // calling rt_alloc()/rt_free() concurrently in their calc function!
        rt_free(nullptr, cmd);

        cmd = next;
    }
    mPrivate = nullptr;
}


void sc_synth::finalize() {
    if (initialized) {
        std::for_each(units, units + unit_count, [](Unit* unit) {
            sc_ugen_def* def = reinterpret_cast<sc_ugen_def*>(unit->mUnitDef);
            def->destruct(unit);
        });
    }

    sc_factory->free_ugens(unit_count);
    if (sc_factory->is_realtime_synthesis())
        rt_pool.free(mControls);
    else
        free(mControls);
    initialized = false;

    // free queued unit commands
    // AFAICT this can only happen if a Graph is created, Unit commands are sent and the Graph
    // is deleted all at the same time stamp.
    auto* cmd = (sc_unit_cmd*)mPrivate;
    while (cmd) {
        auto next = cmd->next;
        rt_pool.free(cmd);
        cmd = next;
    }
}

void sc_synth::set(slot_index_t slot_index, sample val) {
    if (slot_index >= mNumControls) {
        log("argument number out of range\n");
        return;
    }

    mControlRates[slot_index] = 0;
    mMapControls[slot_index] = &mControls[slot_index];
    mControls[slot_index] = val;
}

void sc_synth::set_control_array(slot_index_t slot_index, size_t count, sample* val) {
    if (slot_index + count >= mNumControls)
        return;
    for (size_t i = 0; i != count; ++i)
        set(slot_index + i, val[i]);
}

float sc_synth::get(slot_index_t slot_index) const { return mControls[slot_index]; }

bool sc_synth::getMappedSymbol(slot_index_t slot_index, char* str) const {
    // analogous place in scsynth: SC_Group.cpp: void Group_QueryTreeAndControls(Group* inGroup, big_scpacket *packet)
    bool isMapped = mMapControls[slot_index] != mControls + slot_index;
    if (isMapped) {
        if (mControlRates[slot_index] == 2) {
            int bus = mMapControls[slot_index] - mNode.mWorld->mAudioBus;
            bus = bus / mNode.mWorld->mBufLength;
            sprintf(str, "a%d", bus);
        } else {
            int bus = mMapControls[slot_index] - mNode.mWorld->mControlBus;
            sprintf(str, "c%d", bus);
        }
    }
    return isMapped;
}

void sc_synth::map_control_bus_control(unsigned int slot_index, int control_bus_index) {
    if (slot_index >= mNumControls)
        return;

    World* world = mNode.mWorld;
    if (control_bus_index < 0) {
        mControlRates[slot_index] = 0;
        mMapControls[slot_index] = mControls + slot_index;
    } else if (uint32(control_bus_index) < world->mNumControlBusChannels) {
        mControlRates[slot_index] = 1;
        mMapControls[slot_index] = world->mControlBus + control_bus_index;
    }
}

void sc_synth::map_control_buses_control(unsigned int slot_index, int control_bus_index, int count) {
    if (slot_index >= mNumControls)
        return;

    int slots_to_set = std::min(count, int(mNumControls - slot_index));

    for (int i = 0; i != slots_to_set; ++i)
        map_control_bus_control(slot_index + i, control_bus_index + i);
}

void sc_synth::map_control_bus_audio(unsigned int slot_index, int audio_bus_index) {
    if (slot_index >= mNumControls)
        return;

    World* world = mNode.mWorld;

    if (audio_bus_index < 0) {
        mControlRates[slot_index] = 0;
        mMapControls[slot_index] = mControls + slot_index;
    } else if (uint(audio_bus_index) < world->mNumAudioBusChannels) {
        mControlRates[slot_index] = 2;
        // lets AudioControl know that we've been mapped into an audio bus, so we can
        // zero the output when it's freed later.
        mAudioBusOffsets[slot_index] = audio_bus_index;
        mMapControls[slot_index] = world->mAudioBus + (audio_bus_index * world->mBufLength);
    }
}

void sc_synth::map_control_buses_audio(unsigned int slot_index, int audio_bus_index, int count) {
    if (slot_index >= mNumControls)
        return;

    int slots_to_set = std::min(count, int(mNumControls - slot_index));

    for (int i = 0; i != slots_to_set; ++i)
        map_control_bus_audio(slot_index + i, audio_bus_index + i);
}

void sc_synth::apply_unit_cmd(const char* unit_cmd, unsigned int unit_index, struct sc_msg_iter* args) {
    if (unit_index >= 0 && unit_index < unit_count) {
        if (!initialized) {
            // don't run the unit command if the unit constructor hasn't been called yet!
            // instead we put it on a queue and run it after the graph has been prepared.
            // NOTE: we can't just use rt_pool.malloc() because other synths might be
            // calling rt_alloc()/rt_free() concurrently in their calc function!
            // log_printf("queue unit command\n");
            auto cmd = (sc_unit_cmd*)rt_alloc(nullptr, sizeof(sc_unit_cmd) + args->size);
            cmd->next = nullptr;
            cmd->size = args->size;
            memcpy(cmd->data, args->data, args->size);
            if (mPrivate) {
                // add to tail
                auto ptr = (sc_unit_cmd*)mPrivate;
                while (ptr->next)
                    ptr = ptr->next;
                ptr->next = cmd;
            } else {
                mPrivate = cmd;
            }
            return;
        }
        Unit* unit = units[unit_index];
        sc_ugen_def* def = reinterpret_cast<sc_ugen_def*>(unit->mUnitDef);

        def->run_unit_command(unit_cmd, unit, args);
    } else {
        log_printf("unit index %d out of range!\n", unit_index);
    }
}

void sc_synth::run(void) { perform(); }

extern spin_lock log_guard;

#if defined(__GNUC__) && !defined(__APPLE__)
#    define thread_local __thread
#endif

#ifdef thread_local
static thread_local std::array<char, 262144> trace_scratchpad;
#else
static std::array<char, 262144> trace_scratchpad;
#endif

struct scratchpad_printer {
    scratchpad_printer(char* str): string(str), position(0) { clear(); }

    void printf(const char* fmt, ...) {
        va_list vargs;
        va_start(vargs, fmt);
        printf(fmt, vargs);
        va_end(vargs);
    }

    const char* data(void) const { return string; }

    bool shouldFlush(void) const { return position + 1024 > 32768; }

    void clear(void) {
        position = 0;
        string[0] = '\0'; // zero-terminated
    }

private:
    void printf(const char* fmt, va_list vargs) { position += vsprintf(string + position, fmt, vargs); }

    char* string;
    int position;
};

void sc_synth::run_traced(void) {
    trace = 0;

#ifndef thread_local
    spin_lock::scoped_lock lock(log_guard);
#endif

    scratchpad_printer printer(trace_scratchpad.data());

    printer.printf("\nTRACE %d  %s    #units: %d\n", id(), this->definition_name(), calc_unit_count);

    for (size_t calc_unit_index = 0; calc_unit_index != calc_unit_count; ++calc_unit_index) {
        Unit* unit = calc_units[calc_unit_index];

        sc_ugen_def* def = reinterpret_cast<sc_ugen_def*>(unit->mUnitDef);
        printer.printf("  unit %zd %s\n    in ", calc_unit_index, def->name());
        for (uint16_t j = 0; j != unit->mNumInputs; ++j) {
            printer.printf(" %g", unit->mInBuf[j][0]);
            if (printer.shouldFlush()) {
#ifdef thread_local
                spin_lock::scoped_lock lock(log_guard);
#endif
                log(printer.data());
                printer.clear();
            }
        }

        printer.printf("\n");

        (unit->mCalcFunc)(unit, unit->mBufLength);

        printer.printf("    out");
        for (int j = 0; j < unit->mNumOutputs; ++j) {
            printer.printf(" %g", unit->mOutBuf[j][0]);
            if (printer.shouldFlush()) {
#ifdef thread_local
                spin_lock::scoped_lock lock(log_guard);
#endif
                log(printer.data());
                printer.clear();
            }
        }
        printer.printf("\n");
    }
    printer.printf("\n");

#ifdef thread_local
    spin_lock::scoped_lock lock(log_guard);
#endif
    log(printer.data());
}

} /* namespace nova */
