// © Copyright The University of New South Wales 2018
//
// 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 3 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. If not, see http://www.gnu.org/licenses/.
//


// Firmata-for-Max Library headers
#include "maxlib.h"

// required Max core headers
#include "ext.h"							// standard Max include, always required
#include "ext_obex.h"						// required for new style Max object


////////////////////////// object struct
typedef struct boardout
{
    // the Max object itself (must be first)
    t_object                    ob;
    
    // Arduino board connection
    board_t                     *board_ob;
    
    // selected pins to write
    long                        pinsCount;
    long                        pinsArray[TOTAL_OUT_PINS];
    long                        valuesArray[TOTAL_OUT_PINS];
    long                        modesArray[TOTAL_OUT_PINS];
    int                         port_index;
    
    // boardin attributes:
    t_symbol                    *att_portname;
    t_symbol                    *att_baudrate;  // Sets the data rate in bits per second (baud) for serial data transmission.
                                                // For communicating with the computer, one of these rates: 300, 600, 1200,
                                                // 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200.
    long                        att_digitalPins[TOTAL_OUT_PINS], digitalPinsCount;
    long                        att_servoPins[TOTAL_OUT_PINS], servoPinsCount;
    long                        att_pwmPins[TOTAL_OUT_PINS], pwmPinsCount;
    
    int                         isopen;

    
    // hardware attributes:
    //t_symbol                    *att_pin_modes[TOTAL_OUT_PINS];
    
} t_boardout;

///////////////////////// function prototypes
//// standard set
void *boardout_new(t_symbol *s, long argc, t_atom *argv);
void boardout_free(t_boardout *x);
void boardout_assist(t_boardout *x, void *b, long m, long a, char *s);
void boardout_ports(t_boardout *x);
void boardout_pins(t_boardout *x);
void boardout_config(t_boardout *x);
void boardout_test(t_boardout *x);
void boardout_open(t_boardout *x);
void boardout_close(t_boardout *x);

//privates
void boardout_printAvailPorts(t_boardout *x);
void boardout_updatePin(t_boardout *x, int index, int val);
void boardout_updateServo(t_boardout *x, int index, int val);
void boardout_updateDigital(t_boardout *x, int index, int val);
void boardout_updatePwm(t_boardout *x, int index, int val);
int  boardout_parseArgs(t_boardout *x, long argc, t_atom *argv);
int  boardout_getBaudRate(t_boardout *x);
bool boardout_pinNumAlreadyExists(t_boardout *x, int pin);
int  boardout_pinDigitalRange(int pin);
int  boardout_pinServoRange(int pin);
int  boardout_pinPwmRange(int pin);
int  boardout_getBoard(t_boardout *x, int len, int *pins, int *modes);
void boardout_init(t_boardout *x);
void boardout_pinNumConvert(int pin, char *str);
void boardout_disconnect(t_boardout *x, char *msg);

void boardout_in1(t_boardout *x, long n);
void boardout_in2(t_boardout *x, long n);
void boardout_in3(t_boardout *x, long n);
void boardout_in4(t_boardout *x, long n);
void boardout_in5(t_boardout *x, long n);
void boardout_in6(t_boardout *x, long n);
void boardout_in7(t_boardout *x, long n);
void boardout_in8(t_boardout *x, long n);
void boardout_in9(t_boardout *x, long n);
void boardout_in10(t_boardout *x, long n);
void boardout_in11(t_boardout *x, long n);
void boardout_in12(t_boardout *x, long n);

//////////////////////// global class pointer variable
void *boardout_class;

void ext_main(void *r)
{
    t_class *c;
    
    c = class_new("cr.boardout", (method)boardout_new, (method)boardout_free,
                  (long)sizeof(t_boardout),
                  0L /* leave NULL!! */, A_GIMME, 0);
    
    /* you CAN'T call this from the patcher */
    class_addmethod(c, (method)boardout_assist,      "assist",  A_CANT,     0);
    class_addmethod(c, (method)boardout_ports,       "ports",               0);
    class_addmethod(c, (method)boardout_pins,        "pins",                0);
    class_addmethod(c, (method)boardout_config,      "config",              0);
    class_addmethod(c, (method)boardout_test,        "test",                0);
    class_addmethod(c, (method)boardout_open,        "open",                0);
    class_addmethod(c, (method)boardout_close,       "close",               0);
    
    class_addmethod(c, (method)boardout_in1,         "in1",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in2,         "in2",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in3,         "in3",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in4,         "in4",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in5,         "in5",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in6,         "in6",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in7,         "in7",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in8,         "in8",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in9,         "in9",     A_LONG,     0);
    class_addmethod(c, (method)boardout_in10,        "in10",    A_LONG,     0);
    class_addmethod(c, (method)boardout_in11,        "in11",    A_LONG,     0);
    class_addmethod(c, (method)boardout_in12,        "in12",    A_LONG,     0);
    
    class_register(CLASS_BOX, c); /* CLASS_NOBOX */
    boardout_class = c;
    
    post("Arduino UNO boardout object created (Writer).");
    
}

void boardout_assist(t_boardout *x, void *b, long m, long a, char *s)
{
    if (m == ASSIST_INLET) { // inlet
        
        if(a == 0) {
            sprintf(s, "inlet %ld - no pins", a);
        }
        else {
            
            char *str;
            
            // pin index is inlet-1
            int index = a-1;
            int pin = x->pinsArray[index];
            
            if(pin<14) str = "";
            else {
                str = "A";
                pin -= 14;
            }
            
            switch(x->modesArray[index]) {
                    
                case MODE_OUTPUT:
                    sprintf(s, "pin %s%d mode DIGITAL values {0,1}", str, pin);
                    break;
                case MODE_SERVO:
                    sprintf(s, "pin %s%d mode SERVO range [0-180] degrees", str, pin);
                    break;
                case MODE_PWM:
                    sprintf(s, "pin D%d mode PWM range [0-255]", pin);
                    break;
                default:
                    sprintf(s, "pin %s%d mode UNKNOWN", str, pin);
            }
        }
    }
    else {	// outlet
        sprintf(s, "I am outlet %ld", a);
    }
}

void boardout_free(t_boardout *x)
{
    char msg[4000];
    boardout_disconnect(x, msg);
    if (DEBUG) post("boardout object deleted"); //debug

}

void *boardout_new(t_symbol *s, long argc, t_atom *argv)
{
    t_boardout *x = (t_boardout *)object_alloc(boardout_class);
    
    // verify memory allocated successfully
    if(x == NULL) {
        post( "Max is out of memory, could not create new boardout object.");
        return NULL;
    }
    
    boardout_init(x); // todo - remove, for debug
    
    x->board_ob = NULL;
    x->port_index = -1;
    
    // parse input arguments to set up object
    if (boardout_parseArgs(x, argc, argv) == -1) {
        //Error - Invalid arguments
        object_error((t_object *)x, "Boardout args are invalid, could not create new boardout object.");
        //object_free(x);
        return (x);
    }
    
    
    /* init all pin modes to DIGITAL MODE at first
    for (int i = 0; i < TOTAL_OUT_PINS; ++i) {
        x->att_pin_modes[i] = gensym("DIGITAL");
    }*/
    
    // debug:
    //if(x->att_portname && x->att_baudrate) {
    //    object_post((t_object *)x, "port name: %s\t rate: %d", x->att_portname->s_name, atoi(x->att_baudrate->s_name));
    //}
    
    // create pin integer inlets
    for (int i = x->pinsCount; i > 0; --i) {
        // create int inlets from right to left (highest-right to 1-left):
        intin(x, i);
    }
    
    // boardout object is now all set up
    return (x);
}

void boardout_ports(t_boardout *x){
    boardout_printAvailPorts(x);
}

void boardout_config(t_boardout *x) {
    
    char str[4000], str_pin[10];
    long count, *arr;
    
    str[0] = '\0';
   
    // Digital pins
    count = x->digitalPinsCount;
    arr = x->att_digitalPins;
    if(count) {
        str[0] = '\0';
        strcat(str, "Digital pins: ");
        for (long i=0; i<count; ++i) {
            boardout_pinNumConvert(arr[i], str_pin);
            strcat(str, str_pin);
            if(i < count-1)
                strcat(str, " ,");
        }
        object_post((t_object *)x, "%s", str);
    }
    
    // Servo pins
    count = x->servoPinsCount;
    arr = x->att_servoPins;
    if(count) {
        str[0] = '\0';
        strcat(str, "Servo pins: ");
        for (long i=0; i<count; ++i) {
            boardout_pinNumConvert(arr[i], str_pin);
            strcat(str, str_pin);
            if(i < count-1)
                strcat(str, " ,");
        }
        object_post((t_object *)x, "%s", str);
    }

    // PWM pins
    count = x->pwmPinsCount;
    arr = x->att_pwmPins;
    if(count) {
        str[0] = '\0';
        strcat(str, "PWM pins: ");
        for (long i=0; i<count; ++i) {
            boardout_pinNumConvert(arr[i], str_pin);
            strcat(str, str_pin);
            if(i < count-1)
                strcat(str, " ,");
        }
        object_post((t_object *)x, "%s", str);
    }

    if(strlen(str) == 0) object_post((t_object *)x, "config: no configuration set up yet.");

}

void boardout_pins(t_boardout *x) {
    object_post((t_object *)x, "Possible Modes Configuration:");
    object_post((t_object *)x, "pin 0: @digital.");
    object_post((t_object *)x, "pin 1: @digital.");
    object_post((t_object *)x, "pin 2: @digital, @servo.");
    object_post((t_object *)x, "pin 3: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 4: @digital, @servo.");
    object_post((t_object *)x, "pin 5: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 6: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 7: @digital, @servo.");
    object_post((t_object *)x, "pin 8: @digital, @servo.");
    object_post((t_object *)x, "pin 9: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 10: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 11: @digital, @servo, @pwm.");
    object_post((t_object *)x, "pin 12: @digital, @servo.");
    object_post((t_object *)x, "pin 13: @digital, @servo.");
    object_post((t_object *)x, "pin A0: @digital, @servo.");
    object_post((t_object *)x, "pin A1: @digital, @servo.");
    object_post((t_object *)x, "pin A2: @digital, @servo.");
    object_post((t_object *)x, "pin A3: @digital, @servo.");
    object_post((t_object *)x, "pin A4: @digital, @servo.");
    object_post((t_object *)x, "pin A5: @digital, @servo.");
}

void boardout_test(t_boardout *x){
    if (x == NULL) {
        post("test: board is not connected.");
        return;
    }
    if( ! x->board_ob) {
        object_post((t_object *)x, "test: board is not connected.");
        return;
    }
    
    if ( ! x->board_ob->isBoardReady) {
        object_post((t_object *)x, "test: board is not ready for communication.");
        return;
    }
    
    if( ! x->isopen) {
        object_post((t_object *)x, "test: board is not connected.");
        return;
    }
    object_post((t_object *)x, "test: board is connected.");
    
    char *firmware = firmataclient_getfirmware(x->board_ob);
    object_post((t_object *)x, "test: firmware version %s.", firmware);
    object_post((t_object *)x, "test: board is ready for communication.");
    
    object_post((t_object *)x, "test: onboard LED will now blink 3 times.");
    firmataclient_blink(x->board_ob);
    
    object_post((t_object *)x, "test: Done.");
}

void boardout_open(t_boardout *x){
    if (x == NULL) return;
    
    if(x->isopen) {
        object_post((t_object *)x, "board is already connected.");
        return;
    }
    
    if ( ! x->att_baudrate) {
        // set baud rate to default
        char str[20];
        sprintf(str, "%d", DEFAUTLT_BAUD);
        x->att_baudrate = gensym(str);
    }
    if(DEBUG) object_post((t_object *)x, "baud rate %s.", x->att_baudrate->s_name);
    
    // get updated list of available ports
    char *ports_avail[MAX_PORTS];
    int num_ports = serial_portlist(ports_avail, MAX_PORTS);
    
    if (x->port_index == -1) {
        object_error((t_object *)x, "port not selected.");
        return;
    }
    if(DEBUG) object_post((t_object *)x, "index of port selected %d.", x->port_index);
    
    if (x->port_index < 0 || x->port_index >= num_ports) {
        object_error((t_object *)x, "port selected is out of bounds of available ports.");
        if(DEBUG) object_error((t_object *)x, "index of port selected %d.", x->port_index);
        return;
    }

    // select the port
    x->att_portname = gensym(ports_avail[x->port_index]);
    if(x->att_portname == NULL) {
        if(DEBUG) object_error((t_object *)x, "port can't be found, internal error.");
        return;
    }
    if(DEBUG) object_post((t_object *)x, "port selected %s to open", x->att_portname->s_name); // debug
    
    // check for other connections
    if(x->att_portname->s_thing == NULL) {
        
        // the port is not in use by other boardin/out objects
        if(DEBUG) object_post((t_object *)x, "port has no other connections, %s", x->att_portname->s_name); // debug
        board_mgr_t *mgr = (board_mgr_t*)malloc(sizeof(board_mgr_t));
        if(mgr == NULL) {
            object_error((t_object *)x, "port can't be opened, Max can't allocate memory.");
            return;
        }
        mgr->board_out = NULL;
        mgr->board_in = NULL;
        mgr->board = NULL;
        x->att_portname->s_thing = (t_object*)mgr;
    }
    else {
        // the port is in use by another boardin/out object
        if(DEBUG) object_post((t_object *)x, "OK, port has another connection, %s", x->att_portname->s_name); // debug
    }
    
    
    // set board configuration
    int len_config = x->digitalPinsCount + x->servoPinsCount + x->pwmPinsCount;
    int pins[len_config];
    int modes[len_config];
    
    int len = 0;
    // digital pins
    for(int i=0; i<x->digitalPinsCount; ++i) {
        pins[len] = x->att_digitalPins[i];
        modes[len] = MODE_OUTPUT;
        len++;
    }
    // servo pins
    for(int i=0; i<x->servoPinsCount; ++i) {
        pins[len] = x->att_servoPins[i];
        modes[len] = MODE_SERVO;
        len++;
    }
    // pwm pins
    for(int i=0; i<x->pwmPinsCount; ++i) {
        pins[len] = x->att_pwmPins[i];
        modes[len] = MODE_PWM;
        len++;
    }
    
    //if(DEBUG) object_post((t_object *)x, "%d pins allocated", len); // debug
    //if (DEBUG) {
    //    for(int i=0; i<len; ++i) {
    //        object_post((t_object *)x, "pin %d mode %d", pins[i], modes[i]);
    //    }
    //}

    // create a new writing board and init
    int err = boardout_getBoard(x, len, pins, modes);
    if( err != 0) {
        //object_error((t_object *)x, "Boardout couldn't connect to board.");
        return;
    }

    err = maxclient_connect(x->board_ob, len_config, pins, modes, OUT_BOARD);
    if( err ) {
        object_error((t_object *)x, "open: Boardout couldn't connect to board.");
        if(x->board_ob) object_error((t_object *)x, "%s", x->board_ob->err_msg);
        return;
    }
    
    x->isopen = 1;
    object_post((t_object *)x, "open: boardout is now connected.");
}

void boardout_close_orig(t_boardout *x) {
    
    if(x == NULL) return;
    
    if( ! x->isopen) {
        object_post((t_object *)x, "close: board was not connected.");
        return;
    }
    
    if ( ! x->att_portname) {
        object_post((t_object *)x, "close: no port selected.");
        return;
    }
    
    if(x->att_portname->s_thing == NULL) {
        object_post((t_object *)x, "close: board was not connected.");
        return;
    }
    
    board_mgr_t *mgr = ((board_mgr_t *)(x->att_portname->s_thing));
    if(mgr->board_out == NULL)  {
        object_post((t_object *)x, "close: board was not connected.");
        return;
    }

    x->isopen = 0;
    mgr->board_out = NULL;
    object_post((t_object *)x, "close: diconnected from board.");
    
    
    if(mgr->board_in == NULL) {
        
        // no reader board is connected, release the port altogether
        int err = maxclient_shutdown(x->board_ob);  // close port
        if(err == -1) {
            object_error((t_object *)x, "close: error shutting serial port down");
            if (x->board_ob) object_error((t_object *)x, "%s", x->board_ob->err_msg);
            return;
        }
        
        if (DEBUG) object_post((t_object *)x, "close: port %s was shut down", x->att_portname->s_name);
        
        x->att_portname->s_thing = NULL;
        maxclient_freemgr(mgr);
    }
    
    return;
}

void boardout_close(t_boardout *x) {
    char msg[4000];
    boardout_disconnect(x, msg);
    object_post((t_object *)x, "%s", msg);
}

void boardout_disconnect(t_boardout *x, char *msg) {
    
    if( !x || ! x->isopen) {
        sprintf(msg, "close: board was not connected.");
        return;
    }
    
    if ( ! x->att_portname) {
        sprintf(msg, "close: no port selected.");
        return;
    }
    
    if(x->att_portname->s_thing == NULL) {
        sprintf(msg, "close: board was not connected.");
        return;
    }
    
    board_mgr_t *mgr = ((board_mgr_t *)(x->att_portname->s_thing));
    if(mgr->board_out == NULL)  {
        sprintf(msg, "close: board was not connected.");
        return;
    }
    
    x->isopen = 0;
    mgr->board_out = NULL;

    
    if(mgr->board_in == NULL) {
        
        // no reader board is connected, release the port altogether
        int err = maxclient_shutdown(x->board_ob);  // close port
        if(err == -1) {
            sprintf(msg, "close: error shutting serial port down");
            if (x->board_ob) asprintf(&msg, "%s", x->board_ob->err_msg);
            return;
        }
        
        if (DEBUG) object_post((t_object *)x, "close: port %s was shut down", x->att_portname->s_name);
        
        x->att_portname->s_thing = NULL;
        maxclient_freemgr(mgr);
    }
    
    sprintf(msg, "close: boardout is closed.");
    return;
}


void boardout_updatePin(t_boardout *x, int inlet, int val) {
    
    char *str;
    
    // pin index is inlet-1
    int index = inlet-1;
    
    switch(x->modesArray[index]) {
        case MODE_OUTPUT:
            boardout_updateDigital(x, index, val);
            str = "DIGITAL";
            break;
        case MODE_SERVO:
            boardout_updateServo(x, index, val);
            str = "SERVO";
            break;
        case MODE_PWM:
            boardout_updatePwm(x, index, val);
            str = "PWM";
            break;
        default:
            str = "UNKNOWN";
            post("unidentified mode selected"); // error
    }
    
    if (DEBUG) object_post((t_object *)x, "%s pin %ld: %ld", str, x->pinsArray[index], x->valuesArray[index]); //debug
    
}

void boardout_updateDigital(t_boardout *x, int index, int val) {
    val = (val==0? 0 : 1);
    x->valuesArray[index] = val;
    int pin = x->pinsArray[index];
    maxclient_toggleDigital(x->board_ob, pin, val);
}

void boardout_updateServo(t_boardout *x, int index, int val) {
    x->valuesArray[index] = val;
    int pin = x->pinsArray[index];
    maxclient_moveServo(x->board_ob, pin, val);
}

void boardout_updatePwm(t_boardout *x, int index, int val) {
    x->valuesArray[index] = val;
    int pin = x->pinsArray[index];
    maxclient_movePwm(x->board_ob, pin, val);
}


/*
 PRIVATE INTERNAL FUNCTIONS
 */

int boardout_parseArgs(t_boardout *x, long argc, t_atom *argv)
{
    
    x->port_index = -1;
    
    // set baud rate to default
    char str[20];
    sprintf(str, "%d", DEFAUTLT_BAUD);
    x->att_baudrate = gensym(str);
    
    // parse input arguments
    int i = 0;
    
    // check if the first argument is the port index
    if(argv->a_type == A_SYM){
        char *portCh = atom_getsym(argv)->s_name;
        //object_post((t_object *)x, "port char: %c", portCh[0]); //debug
        int index = portCh[0]-'a';
        //object_post((t_object *)x, "port index: %d", index);  //debug
        
        if (index >= 0 && index <= 'z'-'a') {
            x->port_index = index;
            i = 1;
        }
    }
    
    // Parse DIGITAL SERVO PWM pins lists
    bool servoFound = false, digitalFound = false, pwmFound = false;
    x->pinsCount = 0;
    x->pwmPinsCount = 0;
    x->servoPinsCount = 0;
    x->digitalPinsCount = 0;
    
    for (; i < argc; i++) {
        
        if((argv+i)->a_type == A_SYM) {
            
            t_symbol *s = atom_getsym(argv+i);
            
            // if the symbol is digital
            if( ! strcmp_case_insensitive(s->s_name, "@digital")) {
                //post("Found DIGITAL");
                digitalFound = true;
                servoFound = false;
                pwmFound = false;
            }
            // if symbol is servo
            else if( ! strcmp_case_insensitive(s->s_name, "@servo")) {
                //post("Found SERVO");
                servoFound = true;
                digitalFound = false;
                pwmFound = false;
            }
            // if symbol is pwm
            else if( ! strcmp_case_insensitive(s->s_name, "@pwm")) {
                //post("Found PWM");
                pwmFound = true;
                servoFound = false;
                digitalFound = false;
            }
            // if symbol is baud
            else if( ! strcmp_case_insensitive(s->s_name, "@baud")) {
                //post("Found BAUD");
                digitalFound = false;
                servoFound = false;
                pwmFound = false;
                // check if the baud rate was entered as input
                if((argv+i+1)->a_type == A_LONG) {
                    // move to the next input atom for the baud rate
                    i++;
                    //x->baudrate = atom_getlong(argv+i);
                }
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A0")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 14;
                argv[i] = a[0];
                --i;
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A1")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 15;
                argv[i] = a[0];
                --i;
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A2")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 16;
                argv[i] = a[0];
                --i;
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A3")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 17;
                argv[i] = a[0];
                --i;
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A4")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 18;
                argv[i] = a[0];
                --i;
            }
            else if( ! strcmp_case_insensitive(s->s_name, "A5")) {
                t_atom *a = (t_atom*)malloc(sizeof(t_atom));
                if(a == NULL) return -1;
                a->a_type = A_LONG;
                a->a_w.w_long = 19;
                argv[i] = a[0];
                --i;
            }
            else {
                // otherwise, unknown attribute symbol
                digitalFound = false;
                servoFound = false;
                pwmFound = false;
                // error
                object_error((t_object *)x, "forbidden argument %s", s->s_name);
                //return -1;
            }
        }
        
        else if((argv+i)->a_type == A_LONG) {
            
            int pin = atom_getlong(argv+i);
            
            if((servoFound || digitalFound || pwmFound) && x->pinsCount < TOTAL_OUT_PINS) {
                
                if(servoFound) {
                    if( ! boardout_pinServoRange(pin)) {
                        // error
                        object_error((t_object *)x, "Invalid pin number %d, servo pins range [2-13, A0-A5]", pin);
                        return 0;
                    }
                }
                
                if(pwmFound && !boardout_pinPwmRange(pin)) {
                    // error
                    object_error((t_object *)x, "Invalid pin number %d, pwm pins range [3,5,6,9,10,11]", pin);
                    return 0;
                }
                
                if(digitalFound && !boardout_pinDigitalRange(pin)) {
                    // error
                    object_error((t_object *)x, "Invalid pin number %d, digital pins range [0-13, A0-A5]", pin);
                    return 0;
                }
                
                // also check if the requested pin already exists
                if(boardout_pinNumAlreadyExists(x, pin)) {
                    // error
                    object_error((t_object *)x, "forbidden argument, pin %d already exists", pin);
                }
                
                // we verified the pin does not exist, add it now
                else {
                    x->pinsArray[x->pinsCount] = pin;
                    x->valuesArray[x->pinsCount] = 0;
                    if(servoFound) {
                        x->modesArray[x->pinsCount] = MODE_SERVO;
                        x->att_servoPins[x->servoPinsCount] = pin;
                        x->servoPinsCount++;
                    }
                    else if(digitalFound) {
                        x->modesArray[x->pinsCount] = MODE_OUTPUT;
                        x->att_digitalPins[x->digitalPinsCount] = pin;
                        x->digitalPinsCount++;
                    }
                    else if(pwmFound) {
                        x->modesArray[x->pinsCount] = MODE_PWM;
                        x->att_pwmPins[x->pwmPinsCount] = pin;
                        x->pwmPinsCount++;
                    }
                    
                    x->pinsCount++;
                }
            }
            
            else {
                // error
                if(x->pinsCount >= TOTAL_OUT_PINS) {
                    object_error((t_object *)x, "forbidden argument, too many pins requested");
                    //return -1;
                }
                object_error((t_object *)x, "forbidden argument, number %d is out of context", pin);
                //return -1;
            }
        }
        
        else {
            // error
            object_error((t_object *)x, "forbidden argument, expected format:");
            object_error((t_object *)x, "[port] [@digital pins_list] [@servo pins_list] [@pwm pins_list]");
            //return -1;
            
        }
    }
    return 0;
}

void boardout_printAvailPorts(t_boardout *x) {
    
    char *ports_avail[MAX_PORTS];
    
    int num_ports = serial_portlist(ports_avail, MAX_PORTS);
    
    for(int i=0; i<num_ports; ++i) {
        object_post((t_object *)x, "port %c: %s", i+'a', ports_avail[i]);
    }
}

bool boardout_pinNumAlreadyExists(t_boardout *x, int pin) {
    
    for (int i = 0; i<x->digitalPinsCount; ++i) {
        if(x->att_digitalPins[i] == pin)
            return true;
    }
    
    for (int i = 0; i<x->servoPinsCount; ++i) {
        if(x->att_servoPins[i] == pin)
            return true;
    }
    
    for (int i = 0; i<x->pwmPinsCount; ++i) {
        if(x->att_pwmPins[i] == pin)
            return true;
    }
    
    return false;
}

int boardout_getBoard(t_boardout *x, int len, int *pins, int *modes) {
    
    if(!x || !x->att_portname || !x->att_portname->s_thing) return -1;
    if( !pins || !modes) return -1;
    
    // search symbol port_name. if found, then board is already connected to by a boardin
    // object (reader object), then get the pointer to that board object. Otherwise, create
    // a new board object and initialise it with the requested baud rate and the rest of
    // the pins (write) configuration.
    
    board_mgr_t *mgr = ((board_mgr_t *)(x->att_portname->s_thing));
    if (mgr == NULL) {
        if (DEBUG) object_error((t_object *)x, "can't connect to port, internal error");
        return -1;
    }
    
    if(mgr->board_out != NULL) {
        // error - there is already another boardout object connected
        object_error((t_object *)x, "open: port %s is already occupied with another boardout (writer) object!", x->att_portname->s_name);
        return -1;
    }
    
    else if(mgr->board_in != NULL) {
        
        if (DEBUG) object_post((t_object *)x, "open: port %s has another connection to boardin (okay)", x->att_portname->s_name);   // debug
        
        if(mgr->board == NULL) {
            object_error((t_object *)x, "open: can't connect the board. internal error.");
            return -1;
        }
        
        // check baud rate conflict (between existing and the new board)
        //if(x->att_baudrate) {
        //    long current_rate = atol(x->att_baudrate->s_name);
        //    if(mgr->board->port) {
        //       long port_rate = mgr->board->port->baud_rate;
        //        if(port_rate != current_rate) {
        //            object_error((t_object *)x, "open: can't connect the board, baud rate conflict. ");
        //            object_error((t_object *)x, "port baud rate %ld, but baud %ld requested. ", mgr->board->port->baud_rate, current_rate);
        //            return -1;
        //        }
        //    }
        // }
        
        // check if there is any configuration conflict
        if (maxclient_verifyconfig(mgr->board, len, pins, modes)) {
            object_error((t_object *)x, "open: can't connect the board. ");
            object_error((t_object *)x, "%s", mgr->board->err_msg);
            return -1;
        }

        x->board_ob = mgr->board;
        mgr->board_out = x;
    }
    
    else {
        
        // requested port is not connected to by any other board object, then
        // create a new Arduino board object,  initialise and connect to (serial) port
        
        if (DEBUG) object_post((t_object *)x, "open: port is not connected to by any other board object yet, establishing a new connection."); // debug
        
        char err_return[4000];
        long rate = x->att_baudrate? atol(x->att_baudrate->s_name) : DEFAUTLT_BAUD;
        x->board_ob = maxclient_newboard(x->att_portname->s_name, rate, err_return);
        if(x->board_ob == NULL) {
            // error there is already another boardout object connected
            object_error((t_object *)x, "Max could not create new boardout object.");
            object_error((t_object *)x, "%s", err_return);
            return -1;
        }
        
        //board_mgr_t *mgr = (board_mgr_t*)malloc(sizeof(board_mgr_t));
        mgr->board = x->board_ob;
        mgr->board_out = x;
    }
    
    return 0;
}

int boardout_pinDigitalRange(int pin) {
    return ((pin < 20 && pin >=0)? 1 : 0);
}

int boardout_pinServoRange(int pin) {
    return ((pin < 20 && pin >=2)? 1 : 0);
}

int boardout_pinPwmRange(int pin) {
    // range = {3, 5, 6, 9, 10, 11};
    return ((pin==3 || pin==5 || pin==6 || pin==9 || pin==10 || pin==11)? 1 : 0);
}

void boardout_init(t_boardout *x) {
    
    if(x == NULL)
        return;

    x->pinsCount = 0;
    x->att_portname = NULL;
    x->att_baudrate = NULL;
    x->digitalPinsCount = 0;
    x->pwmPinsCount = 0;
    x->servoPinsCount = 0;
    x->isopen = 0;
}

/*
 INTERNALS
 */

t_max_err boardout_digitalPins_get(t_boardout *x, t_object *attr, long *argc, t_atom **argv) {
    return MAX_ERR_NONE;
}

t_max_err boardout_digitalPins_set(t_boardout *x, t_object *attr, long argc, t_atom *argv) {
    return MAX_ERR_NONE;
}

t_max_err boardout_servoPins_get(t_boardout *x, t_object *attr, long *argc, t_atom **argv) {
    return MAX_ERR_NONE;
}

t_max_err boardout_servoPins_set(t_boardout *x, t_object *attr, long argc, t_atom *argv) {
    return MAX_ERR_NONE;
}

t_max_err boardout_pwmPins_get(t_boardout *x, t_object *attr, long argc, t_atom *argv) {
    return MAX_ERR_NONE;
}

t_max_err boardout_pwmPins_set(t_boardout *x, t_object *attr, long argc, t_atom *argv) {
    return MAX_ERR_NONE;
}

void boardout_in1(t_boardout *x, long n) {
    boardout_updatePin(x, 1, n);
}

void boardout_in2(t_boardout *x, long n) {
    boardout_updatePin(x, 2, n);
}

void boardout_in3(t_boardout *x, long n) {
    boardout_updatePin(x, 3, n);
}

void boardout_in4(t_boardout *x, long n) {
    boardout_updatePin(x, 4, n);
}

void boardout_in5(t_boardout *x, long n) {
    boardout_updatePin(x, 5, n);
}

void boardout_in6(t_boardout *x, long n) {
    boardout_updatePin(x, 6, n);
}

void boardout_in7(t_boardout *x, long n) {
    boardout_updatePin(x, 7, n);
}

void boardout_in8(t_boardout *x, long n) {
    boardout_updatePin(x, 8, n);
}

void boardout_in9(t_boardout *x, long n) {
    boardout_updatePin(x, 9, n);
}

void boardout_in10(t_boardout *x, long n) {
    boardout_updatePin(x, 10, n);
}

void boardout_in11(t_boardout *x, long n) {
    boardout_updatePin(x, 11, n);
}

void boardout_in12(t_boardout *x, long n) {
    boardout_updatePin(x, 12, n);
}

void boardout_pinNumConvert(int pin, char *str) {
    if( ! str) {
        str = "";
        return;
    }
    if(pin < 14) sprintf(str, "%d", pin);
    else sprintf(str, "A%d", pin-14);
    return;
}


// Servo.Write() writes a value to the servo, controlling the shaft accordingly. On a standard servo, this will set the angle of the shaft (in degrees), moving the shaft to that orientation. On a continuous rotation servo, this will set the speed of the servo (with 0 being full-speed in one direction, 180 being full speed in the other, and a value near 90 being no movement).

// PWM: call analogWrite(pin, dutyCycle), where dutyCycle is a value from 0 to 255.
