/*
 * Copyright (c) 2012-2022 Dan Wilcox <danomatika@gmail.com>
 *
 * BSD Simplified License.
 * For information on usage and redistribution, and for a DISCLAIMER OF ALL
 * WARRANTIES, see the file, "LICENSE.txt," in this distribution.
 *
 * See https://github.com/libpd/libpd for documentation
 *
 * This file was originally written for the ofxPd openFrameworks addon:
 * https://github.com/danomatika/ofxPd
 *
 */
#pragma once

#include <string>
#include <vector>
#include <iostream>
#include <sstream>

namespace pd {

/// \section Pd Patch

/// a pd patch
///
/// if you use the copy constructor/operator, keep in mind the libpd void*
/// pointer patch handle is copied and problems can arise if one object is used
/// to close a patch that other copies may be referring to
class Patch {

public:

    Patch() :
        _handle(NULL), _dollarZero(0), _dollarZeroStr("0"),
        _filename(""), _path("") {}

    Patch(const std::string &filename, const std::string &path) :
        _handle(NULL), _dollarZero(0), _dollarZeroStr("0"),
        _filename(filename), _path(path) {}

    Patch(void *handle, int dollarZero,
          const std::string &filename,
          const std::string &path) :
        _handle(handle), _dollarZero(dollarZero),
        _filename(filename), _path(path) {
            std::stringstream itoa;
            itoa << dollarZero;
            _dollarZeroStr = itoa.str();
        }

    /// get the raw pointer to the patch instance
    void* handle() const {return _handle;}

    /// get the unqiue instance $0 ID
    int dollarZero() const {return _dollarZero;}

    /// get the patch filename
    std::string filename() const {return _filename;}

    /// get the parent dir path for the file
    std::string path() const {return _path;}

    /// get the unique instance $0 ID as a string
    std::string dollarZeroStr() const {return _dollarZeroStr;}

    /// is the patch pointer valid?
    bool isValid() const {return _handle != NULL;}

    /// clear patch pointer and dollar zero (does not close patch!)
    ///
    /// note: does not clear filename and path so the object can be reused
    //        for opening multiple instances
    void clear()  {
        _handle = NULL;
        _dollarZero = 0;
        _dollarZeroStr = "0";
    }

    /// copy constructor
    Patch(const Patch &from) {
        _handle = from._handle;
        _dollarZero = from._dollarZero;
        _dollarZeroStr = from._dollarZeroStr;
        _filename = from._filename;
        _path = from._path;
    }

    /// copy operator
    void operator=(const Patch &from) {
        _handle = from._handle;
        _dollarZero = from._dollarZero;
        _dollarZeroStr = from._dollarZeroStr;
        _filename = from._filename;
        _path = from._path;
    }

    /// print info to ostream
    friend std::ostream& operator<<(std::ostream &os, const Patch &from) {
        return os << "Patch: \"" << from.filename() << "\" $0: "
                  << from.dollarZeroStr() << " valid: " << from.isValid();
    }

private:

    void *_handle;              ///< patch handle pointer
    int _dollarZero;            ///< the unique $0 patch ID
    std::string _dollarZeroStr; ///< $0 as a string

    std::string _filename;      ///< filename
    std::string _path;          ///< full path to parent folder
};

/// \section Pd stream interface message objects

/// bang event
struct Bang {

    const std::string dest; ///< dest receiver name

    explicit Bang(const std::string &dest) : dest(dest) {}
};

/// float value
struct Float {

    const std::string dest; ///< dest receiver name
    const float num;        ///< the float value

    Float(const std::string &dest, const float num) :
        dest(dest), num(num) {}
};

/// symbol value
struct Symbol {

    const std::string dest;   ///< dest receiver name
    const std::string symbol; ///< the symbol value

    Symbol(const std::string &dest, const std::string &symbol) :
        dest(dest), symbol(symbol) {}
};

/// a compound message containing floats and symbols
class List {

public:

    List() {}

/// \section Read

    /// check if index is a float type
    bool isFloat(const unsigned int index) const {
        if(index < objects.size())
            if(objects[index].type == List::FLOAT)
                return true;
        return false;
    }

    /// check if index is a symbol type
    bool isSymbol(const unsigned int index) const {
        if(index < objects.size())
            if(objects[index].type == List::SYMBOL)
                return true;
        return false;
    }

    /// get index as a float
    float getFloat(const unsigned int index) const {
        if(!isFloat(index)) {
            std::cerr << "Pd: List object " << index << " is not a float"
                      << std::endl;
            return 0;
        }
        return objects[index].value;
    }

    /// get index as a symbol
    std::string getSymbol(const unsigned int index) const {
        if(!isSymbol(index)) {
            std::cerr << "Pd: List object " << index << " is not a symbol"
                      << std::endl;
            return "";
        }
        return objects[index].symbol;
    }

/// \section Write
///
/// add elements to the list
///
/// List list;
/// list.addSymbol("hello");
/// list.addFloat(1.23);
///

    /// add a float to the list
    void addFloat(const float num) {
        MsgObject o;
        o.type = List::FLOAT;
        o.value = num;
        objects.push_back(o);
        typeString += 'f';
    }

    /// add a symbol to the list
    void addSymbol(const std::string &symbol) {
        MsgObject o;
        o.type = List::SYMBOL;
        o.symbol = symbol;
        objects.push_back(o);
        typeString += 's';
    }

/// \section Write Stream Interface
///
/// list << "hello" << 1.23;
///

    /// add a float to the message
    List& operator<<(const bool var) {
        addFloat((float) var);
        return *this;
    }

    /// add a float to the message
    List& operator<<(const int var) {
        addFloat((float) var);
        return *this;
    }

    /// add a float to the message
    List& operator<<(const float var) {
        addFloat((float) var);
        return *this;
    }

    /// add a float to the message
    List& operator<<(const double var) {
        addFloat((float) var);
        return *this;
    }

    /// add a symbol to the message
    List& operator<<(const char var) {
        std::string s;
        s = var;
        addSymbol(s);
        return *this;
    }

    /// add a symbol to the message
    List& operator<<(const char *var) {
        addSymbol((std::string) var);
        return *this;
    }

    /// add a symbol to the message
    List& operator<<(const std::string &var) {
        addSymbol((std::string) var);
        return *this;
    }

/// \section Util

    /// return number of items
    unsigned int len() const {return (unsigned int) objects.size();}

    /// return OSC style type string ie "fsfs"
    const std::string& types() const {return typeString;}

    /// clear all objects
    void clear() {
        typeString = "";
        objects.clear();
    }

    /// get list as a string
    std::string toString() const {
        std::string line;
        std::stringstream itoa;
        for(int i = 0; i < (int)objects.size(); ++i) {
            if(isFloat(i)) {
                itoa << getFloat(i);
                line += itoa.str();
                itoa.str("");
            }
            else {
                line += getSymbol(i);
            }
            if(i < (int)objects.size()-1) {
                line += " ";
            }
        }
        return line;
    }

    /// print to ostream
    friend std::ostream& operator<<(std::ostream &os, const List &from) {
        return os << from.toString();
    }

private:

    std::string typeString; ///< OSC style type string

    // object type
    enum MsgType {
        FLOAT,
        SYMBOL
    };

    // object wrapper
    struct MsgObject {
        MsgType type;
        float value;
        std::string symbol;
    };

    std::vector<MsgObject> objects; ///< list objects
};

/// start a compound message
struct StartMessage {
    explicit StartMessage() {}
};

/// finish a compound message as a list
struct FinishList {

    const std::string dest; ///< dest receiver name

    explicit FinishList(const std::string &dest) : dest(dest) {}
};

/// finish a compound message as a typed message
struct FinishMessage {

    const std::string dest; ///< dest receiver name
    const std::string msg;  ///< target msg at the dest

    FinishMessage(const std::string &dest, const std::string &msg) :
            dest(dest), msg(msg) {}
};

/// \section Pd stream interface midi objects
/// ref: http://www.gweep.net/~prefect/eng/reference/protocol/midispec.html

/// send a note on event (set vel = 0 for noteoff)
struct NoteOn {

    const int channel;  ///< channel (0 - 15 * dev#)
    const int pitch;    ///< pitch (0 - 127)
    const int velocity; ///< velocity (0 - 127)

    NoteOn(const int channel, const int pitch, const int velocity=64) :
        channel(channel), pitch(pitch), velocity(velocity) {}
};

/// change a control value aka send a CC message
struct ControlChange {

    const int channel;    ///< channel (0 - 15 * dev#)
    const int controller; ///< controller (0 - 127)
    const int value;      ///< value (0 - 127)

    ControlChange(const int channel, const int controller, const int value) :
        channel(channel), controller(controller), value(value) {}
};

/// change a program value (ie an instrument)
struct ProgramChange {

    const int channel; ///< channel (0 - 15 * dev#)
    const int value;   ///< value (0 - 127)

    ProgramChange(const int channel, const int value) :
        channel(channel), value(value) {}
};

/// change the pitch bend value
struct PitchBend {

    const int channel; ///< channel (0 - 15 * dev#)
    const int value;   ///< value (-8192 - 8192)

    PitchBend(const int channel, const int value) :
        channel(channel), value(value) {}
};

/// change an aftertouch value
struct Aftertouch {

    const int channel; ///< channel (0 - 15 * dev#)
    const int value;   ///< value (0 - 127)

    Aftertouch(const int channel, const int value) :
        channel(channel), value(value) {}
};

/// change a poly aftertouch value
struct PolyAftertouch {

    const int channel; ///< channel (0 - 15 * dev#)
    const int pitch;   ///< pitch (0 - 127)
    const int value;   ///< value (0 - 127)

    PolyAftertouch(const int channel, const int pitch, const int value) :
        channel(channel), pitch(pitch), value(value) {}
};

/// a raw midi byte
struct MidiByte {

    const int port; ///< raw portmidi port
                    ///< see http://en.wikipedia.org/wiki/PortMidi
    const unsigned char byte; ///< the raw midi byte value

    MidiByte(const int port, unsigned char byte) : port(port), byte(byte) {}
};

/// start a raw midi byte stream
struct StartMidi {

    const int port; ///< raw portmidi port

    explicit StartMidi(const int port=0) : port(port) {}
};

/// start a raw sysex byte stream
struct StartSysex {

    const int port; ///< raw portmidi port

    explicit StartSysex(const int port=0) : port(port) {}
};

/// start a sys realtime byte stream
struct StartSysRealTime {

    const int port; ///< raw portmidi port

    explicit StartSysRealTime(const int port=0) : port(port) {}
};

/// finish a midi byte stream
struct Finish {
    explicit Finish() {}
};

} // namespace
