//    Copyright (C) 2013 Andy Pugh

//    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; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
//

/* A component to convert 7i73 bytecodes to bit pins */
#include "rtapi.h"
#include "rtapi_app.h"
#include "hal.h"

#define MAX_CHAN 8

/* module information */
MODULE_AUTHOR("Andy Pugh");
MODULE_DESCRIPTION("Hal-to-text component for Mesa 7i73 and similar");
MODULE_LICENSE("GPL");

typedef struct {
    struct {
        hal_bit_t **key;
        hal_bit_t **rows;
        hal_bit_t **cols;
        hal_u32_t *keycode;
    } hal;
    struct {
        hal_u32_t rollover;
        hal_bit_t invert;
    } param;
    hal_u32_t ncols;
    hal_u32_t nrows;
    hal_u32_t *now;
    hal_u32_t *then;
    hal_bit_t invert;
    char name[HAL_NAME_LEN + 1];
    struct input_dev *key_dev;
    hal_u32_t index;
    int keydown;
    int keyup;
    int rowshift;
    int row;
    int num_keys;
    hal_bit_t scan;
    hal_bit_t keystroke;
}kb_inst_t;

typedef struct {
    kb_inst_t *insts;
    int num_insts;
}kb_t;

static int comp_id;
static kb_t *kb;

char *config[MAX_CHAN];
RTAPI_MP_ARRAY_STRING(config, MAX_CHAN, "screen formatting scancodes")
char *names[MAX_CHAN];
RTAPI_MP_ARRAY_STRING(names, MAX_CHAN, "component names")

void keyup(kb_inst_t *inst){
    int r, c;
    int keycode = *inst->hal.keycode & ~(inst->keydown | inst->keyup);

    r = keycode >> inst->rowshift;
    c = keycode & ~(0xFFFFFFFF << inst->rowshift);
    
    if  (r < 0 
         || c < 0
         || r >= inst->nrows 
         || c >= inst->ncols
         || inst->hal.key[r * inst->ncols + c] == NULL){
        return;
    }
    
    if (inst->num_keys > 0) inst->num_keys--;
    
    *inst->hal.key[r * inst->ncols + c] = 0;
}
void keydown(kb_inst_t *inst){
    int r, c;
    int keycode = *inst->hal.keycode & ~(inst->keydown | inst->keyup);
    
    r = keycode >> inst->rowshift;
    c = keycode & ~(0xFFFFFFFF << inst->rowshift);
    
    if  (r < 0 
         || c < 0
         || r >= inst->nrows 
         || c >= inst->ncols
         || inst->hal.key[r * inst->ncols + c] == NULL){
        return;
    }
    
    if (inst->num_keys >= inst->param.rollover) return;
    inst->num_keys++;
    
    *inst->hal.key[r * inst->ncols + c] = 1;
}

    void loop(void *arg, long period){
    int c;
    hal_u32_t scan = 0;
    kb_inst_t *inst = arg;
    
    if (inst->scan){ //scanning request
        for (c = 0; c < inst->ncols; c++){
            scan += ((*inst->hal.cols[c] != inst->param.invert) << c);
        }
        if (scan == inst->now[inst->row] && scan != inst->then[inst->row]){
            // debounced and changed
            for (c = 0; c < inst->ncols; c++){
                int mask = 1 << c;
                if ((inst->then[inst->row] & mask) && !(scan & mask)){ //keyup
                    *inst->hal.keycode = inst->keyup 
                    + (inst->row << inst->rowshift) 
                    + c;
                    keyup(inst);
                }
                else if (!(inst->then[inst->row] & mask) && (scan & mask)){//keydown
                    *inst->hal.keycode = inst->keydown 
                    + (inst->row << inst->rowshift) 
                    + c;
                    
                    keydown(inst);
                }
            }
        }
        
        inst->then[inst->row] = inst->now[inst->row];
        inst->now[inst->row] = scan;
        
        *inst->hal.rows[inst->row] = inst->param.invert;
        inst->row++;
        if (inst->row >= inst->nrows) inst->row = 0;
        *inst->hal.rows[inst->row] = !inst->param.invert;
    }
    else
    {
        if (*inst->hal.keycode == 0x40) return;
        if ((*inst->hal.keycode & inst->keydown) == inst->keydown){
            keydown(inst);
        }
        else if ((*inst->hal.keycode & inst->keydown) == inst->keyup)
        {
            keyup(inst);
        }
    }
}


int rtapi_app_main(void){
    int i, j, n;
    int retval;
    comp_id = hal_init("matrix_kb");
    if (comp_id < 0) {
        rtapi_print_msg(RTAPI_MSG_ERR, "matrix_kb: ERROR: hal_init() failed\n");
        return -1;
    }
    
    // allocate shared memory for data
    kb = hal_malloc(sizeof(kb_t));
    if (kb == 0) {
        rtapi_print_msg(RTAPI_MSG_ERR,
                        "matrix_kb component: Out of Memory\n");
        hal_exit(comp_id);
        return -1;
    }
    
    // Count the instances.
    for (kb->num_insts = 0; config[kb->num_insts];kb->num_insts++);
    // Count the names.
    for (n = 0; names[n];n++);
    
    if (n && n != kb->num_insts){
        rtapi_print_msg(RTAPI_MSG_ERR, "matrix_kb: Number of sizes and number"
                        " of names must match\n");
        hal_exit(comp_id);
        return -1;
    }
    
    kb->insts = hal_malloc(kb->num_insts * sizeof(kb_inst_t));
    
    for (i = 0; i < kb->num_insts; i++){
        int a = 0;
        int c, r;
        kb_inst_t *inst = &kb->insts[i];

        inst->index = i;
        inst->nrows = 0;
        inst->ncols = 0;
        inst->scan = 0;
        inst->keystroke = 0;
        inst->param.invert = 1;
        
        for(j = 0; config[i][j] !=0; j++){
            int n = (config[i][j] | 0x20); //lower case
            if (n == 'x'){
                inst->nrows = a;
                a = 0;
            }
            else if (n >= '0' && n <= '9'){
                a = (a * 10) + (n - '0');
            }
            else if (n == 's'){
                inst->scan = 1;
            }
        }
        inst->ncols = a;
        
        if (inst->ncols == 0 || inst->nrows == 0){
            rtapi_print_msg(RTAPI_MSG_ERR,
                            "matrix_kb: Invalid size format. should be NxN\n");
            hal_exit(comp_id);
            return -1;
        }
        
        if (inst->ncols > 32){
            rtapi_print_msg(RTAPI_MSG_ERR,
                            "matrix_kb: maximum number of columns is 32. Sorry\n");
            hal_exit(comp_id);
            return -1;
        }
        
        for (inst->rowshift = 1; inst->ncols > (1 << inst->rowshift); inst->rowshift++);
        for (inst->keydown = 0xC0, inst->keyup = 0x80
             ; (inst->nrows << inst->rowshift) > inst->keydown
             ; inst->keydown <<= 1, inst->keyup <<= 1);
        
        inst->hal.key = (hal_bit_t **)hal_malloc(inst->nrows * inst->ncols * sizeof(hal_bit_t*));
        inst->now = hal_malloc(inst->nrows * sizeof(hal_u32_t));
        inst->then = hal_malloc(inst->nrows * sizeof(hal_u32_t));
        inst->row = 0;
        inst->param.rollover = 2;
        
        
        if (names[i]){
            rtapi_snprintf(inst->name, sizeof(inst->name), "%s", names[i]);
        }
        else
        {
            rtapi_snprintf(inst->name, sizeof(inst->name), "matrix_kb.%i", i);
        }
        
        for (c = 0; c < inst->ncols; c++){
            for (r = 0; r < inst->nrows; r++){  
                retval = hal_pin_bit_newf(HAL_OUT,
                                          &(inst->hal.key[r * inst->ncols + c]), 
                                          comp_id,
                                          "%s.key.r%xc%x", 
                                          inst->name, r, c);
                if (retval != 0) {
                    rtapi_print_msg(RTAPI_MSG_ERR,
                                    "matrix_kb: Failed to create output pin\n");
                    hal_exit(comp_id);
                    return -1;
                }
            }
        }
        
        if (inst->scan){ //internally generated scanning
            inst->hal.rows = (hal_bit_t **)hal_malloc(inst->nrows * sizeof(hal_bit_t*));
            inst->hal.cols = (hal_bit_t **)hal_malloc(inst->ncols * sizeof(hal_bit_t*));
            
            for (r = 0; r < inst->nrows; r++){
                retval = hal_pin_bit_newf(HAL_OUT,
                                          &(inst->hal.rows[r]), comp_id,
                                          "%s.row-%02i-out",inst->name, r);
                if (retval != 0) {
                    rtapi_print_msg(RTAPI_MSG_ERR,
                                    "matrix_kb: Failed to create output row pin\n");
                    hal_exit(comp_id);
                    return -1;
                }
            }
            for (c = 0; c < inst->ncols; c++){
                retval = hal_pin_bit_newf(HAL_IN,
                                          &(inst->hal.cols[c]), comp_id,
                                          "%s.col-%02i-in",inst->name, c);
                if (retval != 0) {
                    rtapi_print_msg(RTAPI_MSG_ERR,
                                    "matrix_kb: Failed to create input col pin\n");
                    hal_exit(comp_id);
                    return -1;
                }
            }
                
            retval = hal_pin_u32_newf(HAL_OUT,
                                      &(inst->hal.keycode), comp_id,
                                      "%s.keycode",inst->name);
            if (retval != 0) {
                rtapi_print_msg(RTAPI_MSG_ERR,
                                "matrix_kb: Failed to create output pin\n");
                hal_exit(comp_id);
                return -1;
            }
            
            retval = hal_param_bit_newf(HAL_RW,
                                      &(inst->param.invert), comp_id,
                                      "%s.negative-logic",inst->name);
            if (retval != 0) {
                rtapi_print_msg(RTAPI_MSG_ERR,
                                "matrix_kb: Failed to create output pin\n");
                hal_exit(comp_id);
                return -1;
            }
            
            
            retval = hal_param_u32_newf(HAL_RW,
                                      &(inst->param.rollover), comp_id,
                                      "%s.key_rollover",inst->name);
            if (retval != 0) {
                rtapi_print_msg(RTAPI_MSG_ERR,
                                "matrix_kb: Failed to create rollover param\n");
                hal_exit(comp_id);
                return -1;
            }
            
        }
        else // scanning by 7i73 or similar
        {
            retval = hal_pin_u32_newf(HAL_IN,
                                      &(inst->hal.keycode), comp_id,
                                      "%s.keycode",inst->name);
            if (retval != 0) {
                rtapi_print_msg(RTAPI_MSG_ERR,
                                "matrix_kb: Failed to create input pin\n");
                hal_exit(comp_id);
                return -1;
            }
        }
        
        retval = hal_export_funct(inst->name, loop, inst, 1, 0, comp_id); //needs fp?
        if (retval < 0) {
            rtapi_print_msg(RTAPI_MSG_ERR, "matrix_kb: ERROR: function export failed\n");
            return -1;
        }
    }
    hal_ready(comp_id);
    
    return 0;
}

void rtapi_app_exit(void)
{
    hal_exit(comp_id);
}
