// © 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 boardin
{
    // the Max object itself (must be first)
    t_object                    ob;
    
    // Arduino board connection
    board_t                     *board_ob;
    
    // selected pins to read
    void                        **outlets;
    long                        pinsCount;
    long                        pinsArray[TOTAL_IN_PINS];
    long                        valuesArray[TOTAL_IN_PINS];
    long                        modesArray[TOTAL_IN_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_IN_PINS], digitalPinsCount;
    long                        att_analogPins[TOTAL_IN_PINS], analogPinsCount;
    
    int                         isopen;
    
} t_boardin;

///////////////////////// function prototypes
//// Max standard set
void *boardin_new(t_symbol *s, long argc, t_atom *argv);
void boardin_free(t_boardin *x);
void boardin_assist(t_boardin *x, void *b, long m, long a, char *s);
void boardin_ports(t_boardin *x);
void boardin_pins(t_boardin *x);
void boardin_config(t_boardin *x);
void boardin_bang(t_boardin *x);
void boardin_test(t_boardin *x);
void boardin_open(t_boardin *x);
void boardin_close(t_boardin *x);

//privates
void boardin_printAvailPorts(t_boardin *x);
void boardin_updatePin(t_boardin *x, int index, int val);
int  boardin_parseArgs(t_boardin *x, long argc, t_atom *argv);
int  boardin_getBaudRate(t_boardin *x);
bool boardin_pinNumAlreadyExists(t_boardin *x, int pin);    // todo - check with boardout pins (handle duplicates)?
void boardin_init(t_boardin *x);
void boardin_connectBoard(t_boardin *x, int len, int *pins, int *modes);
int  boardin_pinDigitalRange(int pin);
int  boardin_pinAnalogRange(int pin);
void boardin_pinNumConvert(int pin, char *str);
void boardin_disconnect(t_boardin *x, char *err_msg);


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

void ext_main(void *r)
{
    t_class *c;
    
    c = class_new("cr.boardin", (method)boardin_new, (method)boardin_free,
                  (long)sizeof(t_boardin),
                  0L /* leave NULL!! */, A_GIMME, 0);
    
    /* you CAN'T call this from the patcher */
    class_addmethod(c, (method)boardin_assist,      "assist",  A_CANT,     0);
    class_addmethod(c, (method)boardin_ports,       "ports",               0);
    class_addmethod(c, (method)boardin_pins,        "pins",                0);
    class_addmethod(c, (method)boardin_config,      "config",              0);
    class_addmethod(c, (method)boardin_bang,        "bang",                0);
    class_addmethod(c, (method)boardin_test,        "test",                0);
    class_addmethod(c, (method)boardin_open,        "open",                0);
    class_addmethod(c, (method)boardin_close,       "close",               0);

    class_register(CLASS_BOX, c); /* CLASS_NOBOX */
    boardin_class = c;
    
    post("Arduino UNO boardin object created (Reader).");
}

void boardin_assist(t_boardin *x, void *b, long m, long index, char *s)
{
    int pin = x->pinsArray[index];
    char *str;
    
    if(pin<14) str = "";
    else {
        str = "A";
        pin -= 14;
        // analog pins are numbered as:
        // Arduino: A0-A5
        // Firmata: (#14-#19)
    }

    if (m == ASSIST_OUTLET) { // outlet messages
    
        switch(x->modesArray[index]) {
            case MODE_INPUT:
                sprintf(s, "pin %s%d mode DIGITAL values {0,1}", str, pin);
                break;
            case MODE_ANALOG:
                sprintf(s, "pin A%d mode ANALOG range [0-1023]", pin);
                break;
            default:
                sprintf(s, "pin %s%d is INVALID", str, pin);
        }

    }
    else {	// inlet messages
        sprintf(s, "msg inlet");
    }
    
}

void boardin_free(t_boardin *x) {
    char err_msg[4000];
    boardin_disconnect(x, err_msg);
 
    // free allocated outlets
    if (x->outlets) {
        free(x->outlets);
    }
    
    if (DEBUG) post("boardin object deleted."); //debug
}

void *boardin_new(t_symbol *s, long argc, t_atom *argv)
{
    t_boardin *x = (t_boardin *)object_alloc(boardin_class);
    
    // verify memory allocated successfully
    if (x == NULL ) {
        post( "Max is out of memory, could not create new boardin object.");
        return NULL;
    }
   
    boardin_init(x); // todo - remove, for debug
    
    x->board_ob = NULL;
    x->port_index = -1;
    
    // parse input arguments to set up the board configuration
    if (boardin_parseArgs(x, argc, argv) == -1) {
        //Error - Invalid arguments
        object_error((t_object *)x, "Invalid args, could not create new boardin object.");
        //object_free(x);
        return (x);
    }

    // debug:
    //if (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 int outlets from right to left (highest-right to 1-left)
    x->outlets = (void **)malloc(x->pinsCount*sizeof(void *));
    // verify memory allocated successfully
    if (x->outlets == NULL ) {
        object_error((t_object *)x, "Max is out of memory, could not create new boardin object.");
        //boardin_free(x);
        return (x);
    }
    for (int i = x->pinsCount; i > 0;) {
        --i;
        x->outlets[i] = intout(x);
    }
    
    // boardin object is now all set up
    return (x);
}

int boardin_parseArgs(t_boardin *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]);
        int index = portCh[0]-'a';
        //object_post((t_object *)x, "port index: %d", index);
        
        if (index >= 0 && index <= 'z'-'a') {
            x->port_index = index;
            i = 1;
        }
        // check if the baud rate was entered as input
        if((i)< argc && (argv+i)->a_type == A_LONG) {
            long val = atom_getlong(argv+i);
            sprintf(str, "%ld", val);
            x->att_baudrate = gensym(str);
            i = 2;
        }

    }
    
    // Parse DIGITAL and ANALOG pins lists
    bool analogFound = false, digitalFound = false, rangeFound = false;
    x->pinsCount = 0;
    x->analogPinsCount = 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;
                analogFound = false;
                rangeFound = false;
            }
            // if symbol is analog
            else if( ! strcmp_case_insensitive(s->s_name, "@analog")) {
                //post("Found ANALOG");
                analogFound = true;
                digitalFound = false;
                rangeFound = false;
            }
            // if symbol is range
            else if( ! strcmp_case_insensitive(s->s_name, "@range")) {
                //post("Found RANGE");
                rangeFound = true;
                analogFound = false;
                digitalFound = false;
            }
            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;
                analogFound = false;
                rangeFound = 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 (analogFound) {
                if (!boardin_pinAnalogRange(pin)) {
                    // error
                    object_error((t_object *)x, "Invalid pin number %d, analog pins range [A0-A5]", pin);
                    return 0;
                }
            }
            
            if (digitalFound && !boardin_pinDigitalRange(pin)) {
                    // error
                    object_error((t_object *)x, "Invalid pin number %d, digital pins range [0-13, A0-A5]", pin);
                    return 0;
            }
            
            if((analogFound || digitalFound) && x->pinsCount < TOTAL_IN_PINS) {
            
                // first check if the requested pin already exists
                if(boardin_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(analogFound) {
                        x->modesArray[x->pinsCount] = MODE_ANALOG;
                        x->att_analogPins[x->analogPinsCount] = pin;
                        x->analogPinsCount++;
                    }
                    else if(digitalFound) {
                        x->modesArray[x->pinsCount] = MODE_INPUT;
                        x->att_digitalPins[x->digitalPinsCount] = pin;
                        x->digitalPinsCount++;
                    }
                    x->pinsCount++;
                }
            }
            else {
                // error
                if(x->pinsCount >= TOTAL_IN_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] [@analog pins_list]");
            //return -1;
            
        }
    }

    return 0;
}

void boardin_bang(t_boardin *x){
    
    // update input pin values
    maxclient_getInputValues(x->board_ob, x->pinsArray, x->valuesArray, x->pinsCount);
    
    /*
    // todo - remove (for debug only)
    object_post((t_object *)x, "bang!");
    for(int i=0; i<x->pinsCount; ++i) {
        //x->valuesArray[i] = (i+1);
        object_post((t_object *)x, "outlet %d=pin%d=%d", i, x->pinsArray[i], x->valuesArray[i]);
    }*/
    
    t_atom val;
    for(int i=0; i<x->pinsCount; ++i) {
        atom_setlong(&val, x->valuesArray[i]);
        outlet_int(x->outlets[i], atom_getlong(&val));
    }
}

void boardin_ports(t_boardin *x){
    boardin_printAvailPorts(x);
}

void boardin_config(t_boardin *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) {
            boardin_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->analogPinsCount;
    arr = x->att_analogPins;
    if(count) {
        str[0] = '\0';
        strcat(str, "Analog pins: ");
        for (long i=0; i<count; ++i) {
            boardin_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 boardin_pins(t_boardin *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.");
    object_post((t_object *)x, "pin 3: @digital.");
    object_post((t_object *)x, "pin 4: @digital.");
    object_post((t_object *)x, "pin 5: @digital.");
    object_post((t_object *)x, "pin 6: @digital.");
    object_post((t_object *)x, "pin 7: @digital.");
    object_post((t_object *)x, "pin 8: @digital.");
    object_post((t_object *)x, "pin 9: @digital.");
    object_post((t_object *)x, "pin 10: @digital.");
    object_post((t_object *)x, "pin 11: @digital.");
    object_post((t_object *)x, "pin 12: @digital.");
    object_post((t_object *)x, "pin 13: @digital.");
    object_post((t_object *)x, "pin A0: @analog, @digital.");
    object_post((t_object *)x, "pin A1: @analog, @digital.");
    object_post((t_object *)x, "pin A2: @analog, @digital.");
    object_post((t_object *)x, "pin A3: @analog, @digital.");
    object_post((t_object *)x, "pin A4: @analog, @digital.");
    object_post((t_object *)x, "pin A5: @analog, @digital.");
}

void boardin_test(t_boardin *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 boardin_open(t_boardin *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.");
        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
    }

    
    // setup pins configuration as requested
    int len_config = x->digitalPinsCount + x->analogPinsCount;
    int pins[len_config], 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_INPUT;
        len++;
    }
    // analog pins
    for(int i=0; i<x->analogPinsCount; ++i) {
        pins[len] = x->att_analogPins[i]; // + 14;  // analog pins are numbered 14-19 corresponding to A0-A5
        modes[len] = MODE_ANALOG;
        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]);
    //    }
    //}
    
    
    // connect to port
    boardin_connectBoard(x, len, pins, modes);
}

void boardin_close(t_boardin *x) {
    char msg[4000];
    boardin_disconnect(x, msg);
    object_post((t_object *)x, "%s", msg);
}

void boardin_disconnect(t_boardin *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_in == NULL)  {
        sprintf(msg, "close: board was not connected.");
        return;
    }

    int err = maxclient_disconnect(x->board_ob); // kill listener
    if(err == -1) {
        sprintf(msg, "close: error disconnecting");
        if (x->board_ob) asprintf(&msg, ": %s", x->board_ob->err_msg);
        return;
    }
    
    x->isopen = 0;
    mgr->board_in = NULL;
    object_post((t_object *)x, "close: diconnected from board.");
    
    if(mgr->board_out == NULL) {
        
        // no writer board is connected, release the port altogether
        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: boardin is closed.");
    return;
}

void boardin_close_orig(t_boardin *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_in == NULL)  {
        object_post((t_object *)x, "close: board was not connected.");
        return;
    }
    
    int err = maxclient_disconnect(x->board_ob); // kill listener
    if(err == -1) {
        object_error((t_object *)x, "close: error disconnecting");
        if (x->board_ob) object_error((t_object *)x, "%s", x->board_ob->err_msg);
        return;
    }
    
    x->isopen = 0;
    mgr->board_in = NULL;
    object_post((t_object *)x, "close: diconnected from board.");
    
    if(mgr->board_out == NULL) {
        
        // no writer board is connected, release the port altogether
        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 boardin_printAvailPorts(t_boardin *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]);
    }
}

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

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

bool boardin_pinNumAlreadyExists(t_boardin *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->analogPinsCount; ++i) {
        if(x->att_analogPins[i] == pin)
            return true;
    }
    
    return false;
}

void boardin_init(t_boardin *x) {

    if(x == NULL)
        return;
    
    x->outlets = NULL;
    x->pinsCount = 0;
    x->att_portname = NULL;
    x->att_baudrate = NULL;
    x->digitalPinsCount = 0;
    x->analogPinsCount = 0;
    x->isopen = 0;
}

void boardin_connectBoard(t_boardin *x, int len, int *pins, int *modes) {

    if(!x || !x->att_portname || !x->att_portname->s_thing) return;
    if( !pins || !modes) return;
    
    
    // search symbol port_name. if found, then board is already connected to by a boardout
    // object (writer 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 (read) 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;
    }
    
    if(mgr->board_in != NULL) {
        // error - there is already another boardin object connected
        object_error((t_object *)x, "open: port %s is already occupied with another boardin (reader) object!", x->att_portname->s_name);
        return;
    }
    
    else if(mgr->board_out != NULL) {
        
        if (DEBUG) object_post((t_object *)x, "open: port %s is connected to a Writer (okay)", x->att_portname->s_name);   // debug
        
        if(mgr->board == NULL) {
            object_error((t_object *)x, "open: internal error. board can't be reached on %s.", x->att_portname->s_name);
            return;
        }
        
        // check if there is any configuration conflict
        if(mgr->board == NULL) {
            object_error((t_object *)x, "open: can't connect the board. internal error");
            return;
        }
        
        // 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;
        //        }
        //    }
        //}
        
        // 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;
        }
        
        x->board_ob = mgr->board;
        mgr->board_in = 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 any other board 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 boardin object connected
            object_error((t_object *)x, "open: Max could not create new boardin object.");
            object_error((t_object *)x, "%s", err_return);
            return;
        }
        // debug
        //else {
            //if (DEBUG) post("maxclient_newboard succeedded");
        //}
        
        mgr->board = x->board_ob;
        mgr->board_in = x;
    }
    
    // start a new listener thread and
    // set board configuration (i.e., pin modes) as requested
    int err = maxclient_connect(x->board_ob, len, pins, modes, IN_BOARD);
    if(err) {
        object_error((t_object *)x, "open: Boardin couldn't connect to board.");
        if(x->board_ob) object_error((t_object *)x, "%s", x->board_ob->err_msg);
        return;
    }
    
    // debug
    //if (DEBUG) object_post((t_object *)x, "Listening thread started.");
    

    x->isopen = 1;
    object_post((t_object *)x, "open: boardin is now connected.");
}

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

int boardin_pinAnalogRange(int pin) {
    // Analog pins range: 14 [A0] .. 19 [A5]
    return ((pin < 20 && pin >13)? 1 : 0);
}

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


//
// The Arduino board contains a 6 channel, 10-bit analog to digital converter.
// This means that it will map input voltages between 0 and 5 volts into integer values between 0 and 1023.
// 10 bits, pow(2, 10)=1024.
//








