/*
 * utf8 ru
  *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
  ------------------------------------------------------------------------
 * MAX6954 driver API
 * MAX6954 is LCD driver for 16/8 symbols, bicolor,
 *        with keyboard scaner up to 32 keys
 *        with 5 PIN GPIO
 * */

#include <assert.h>
#include <stdint.h>
#include <stddef.h>
#include <c_compat.h>
#include "max6954.h"


//namespace max6954 {
    enum RegAdress{
        // R/W mode
        rRD = 0x80, rWR = 0,

        // adress idx
        rNOP = 0 ,
        rDECODE_MODE ,
        rGLOBAL_INTENSITY ,
        rSCAN_LIMIT ,
        rCTRL, rCONFIGURATION  = rCTRL,
        rGPIO ,
        rPORT_CFG ,
        rDISPLAY_TEST ,

        rKEYA_MASK, rKEYA_DEB = rKEYA_MASK ,
        rKEYB_MASK, rKEYB_DEB = rKEYB_MASK ,
        rKEYC_MASK, rKEYC_DEB = rKEYC_MASK ,
        rKEYD_MASK, rKEYD_DEB = rKEYD_MASK ,

        rDIGIT_TYPE, // write

        rKEYA_PRESS = rDIGIT_TYPE, // read
        rKEYB_PRESS ,
        rKEYC_PRESS ,
        rKEYD_PRESS ,

        rINTENS10, rINTENS32, rINTENS54, rINTENS76,
        rINTENS10A, rINTENS32A, rINTENS54A, rINTENS76A,

        rDIG0P0, rDIG1P0, rDIG2P0, rDIG3P0, rDIG4P0, rDIG5P0, rDIG6P0, rDIG7P0,
        rDIG0AP0, rDIG1AP0, rDIG2AP0, rDIG3AP0, rDIG4AP0, rDIG5AP0, rDIG6AP0, rDIG7AP0,

        rDIG0P1, rDIG1P1, rDIG2P1, rDIG3P1, rDIG4P1, rDIG5P1, rDIG6P1, rDIG7P1,
        rDIG0AP1, rDIG1AP1, rDIG2AP1, rDIG3AP1, rDIG4AP1, rDIG5AP1, rDIG6AP1, rDIG7AP1,

        rDIG0, rDIG1, rDIG2, rDIG3, rDIG4, rDIG5, rDIG6, rDIG7,
        rDIG0A, rDIG1A, rDIG2A, rDIG3A, rDIG4A, rDIG5A, rDIG6A, rDIG7A,
    };

    enum DigitType {
        dt7SEG = 0, dt16SEG = 0,
        dt14SEG = 1 ,
    };

    enum DecodeMode {
        dmNONE = 0 ,
        dmHEX  = 1 ,
        dmNONE_ALL = 0 ,
        dmHEX_ALL  = 0xffu ,
    };

    enum RegINTENSE{
        iINENSE_WIDTH = 4,
        iINENSE_Msk = 0xf,
    };

    enum ConfigFields{
        cfSHUTDOWN          = 0u,
        cfONLINE            = 1u,

        cfBLINK_RATE_Msk    = 4u,
        // 0.5sec for OSC 4MHz
        cfBLINK_RATE_FAST   = cfBLINK_RATE_Msk,
        // 1sec for OSC 4MHz
        cfBLINK_RATE_SLOW   = 0u,

        cfBLINK_ENA         = 8u,
        cfBLINK_OFF         = 0u,

        cfBLINK_SYNC        = 0x10u,
        cfBLINK_NOSYNC      = 0u,

        cfCLEAR             = 0x20u,

        cfINTENSITY_CTRL    = 0x40,
        cfINTENSITY_GLOBAL  = 0u,

        cfBLINK_PHASE       = 0x80u,
    };

    enum RegGPIO{
        gpio0 = 1 ,
        gpio1 = 2 ,
        gpio2 = 4 ,
        gpio3 = 8 ,
        gpio4 = 0x10 ,
        keysIRQ = gpio4 ,
    };

    enum RegPORT_CFG{
        portIN = 1, portOUT = 0,

        portDIR_Pos = 0,
        portDIR_Msk = 0x1f,

        portKEYS_LINES_Pos = 5,
        portKEYS_LINES_Msk = 0xe0,
    };

    enum regDISP_TEST{
        dtNORMAL = 0 ,
        dtTEST   = 1 ,
    } ;

    typedef uint_fast8_t RegAdress;
    typedef uint_fast8_t RegData;

    static
    void write_reg(MAX6954h* io, RegAdress adr, RegData x);
    static
    RegData read_reg(MAX6954h* io, RegAdress adr);

//}; //namespace max6954
//using namespace max6954;

void MAX6954_init(MAX6954h* io,
                SSP_TypeDef* spi,
                GPIO_TypeDef*  cs_port, unsigned cs_pin)
{
    io->io = spi;
    io->io_cs = cs_port;
    io->cs_pin = cs_pin;
    io->blink_tim = NULL;

    io->color_mode  = 0;
    io->digits      = read_reg(io, rSCAN_LIMIT);
    io->digits      = io->digits*2+1;
    io->gpio_mode   = read_reg(io, rPORT_CFG);
    io->gpio_out    = read_reg(io, rGPIO);
}

void MAX6954_reset(MAX6954h* io){
    write_reg(io, rCTRL, cfCLEAR);
    write_reg(io, rDECODE_MODE, dmNONE_ALL);
    RegData width = io->digits;
    if (io->color_mode == MAX6954_sCOLORED_PAIR)
        width = width*2;
    write_reg(io, rSCAN_LIMIT, width-1);
}

// @arg len - use MAX6954_Style
bool MAX6954_set_digit_type_all_7segment(MAX6954h* io, unsigned len){
    RegData colormode = len & ~MAX6954_sLEN_Msk;
    len &= ~MAX6954_sLEN_Msk;

    if (colormode == MAX6954_sCOLORED_PAIR){
        if (len > 4)
            return false;
    }
    else if (colormode != 0){
        if (len > 8)
            return false;
    }
    else {
        if (len > 16)
            return false;
    }
    io->digits = len;
    io->color_mode = colormode;

    unsigned width = io->digits;
    if (io->color_mode == MAX6954_sCOLORED_PAIR)
        width = width*2;
    write_reg(io, rSCAN_LIMIT, width-1);

    return true;
}

void MAX6954_enable_individual_segment_brightness(MAX6954h* io){
    RegData tmp = read_reg(io, rCTRL);
    write_reg(io, rCTRL, (tmp | cfINTENSITY_CTRL) );
}

void MAX6954_control_intensity_plain(MAX6954h* io, MAX6954_IntensitySet* set){
    RegData tmp;
    uint8_t* x = set->seg;
    for (int i = 0 ; i < 16; i+=2){
        tmp = (x[i]&MAX6954_ivLIM) | ((x[i+1]&MAX6954_ivLIM)<<iINENSE_WIDTH);
        write_reg(io, rINTENS10+(i/2), tmp );
    }
}

void MAX6954_control_intensity_color_pair(MAX6954h* io, MAX6954_IntensitySet* set){
    RegData tmp;
    uint8_t* x = set->seg;
    for (int i = 0 ; i < 7; ++i){
        tmp = x[i];
        write_reg(io, rINTENS10+(i/2), tmp );
    }
}

void MAX6954_control_intensity_color_a(MAX6954h* io, MAX6954_IntensitySet* set){
    RegData tmp;
    uint8_t* x = set->seg;
    for (int i = 0 ; i < 16; ++i){
        tmp = (x[i]&MAX6954_ivLIM) | ((x[i+2]&MAX6954_ivLIM)<<iINENSE_WIDTH);
        write_reg(io, rINTENS10+(i/2), tmp );
        ++i;
        tmp = (x[i]&MAX6954_ivLIM) | ((x[i+2]&MAX6954_ivLIM)<<iINENSE_WIDTH);
        write_reg(io, rINTENS10A+(i/2), tmp );
    }
}

void MAX6954_control_intensity(MAX6954h* io, MAX6954_IntensitySet* x){
    if (io->color_mode == 0)
        MAX6954_control_intensity_plain(io, x);
    else if(io->color_mode == MAX6954_sCOLORED_PAIR)
        MAX6954_control_intensity_color_pair(io, x);
    else if (io->color_mode == MAX6954_sCOLORED_A)
        MAX6954_control_intensity_color_a(io, x);
}

void MAX6954_enable_global_segment_brightness(MAX6954h* io){
    RegData tmp = read_reg(io, rCTRL);
    write_reg(io, rCTRL, (tmp & ~cfINTENSITY_CTRL) );
}

void MAX6954_set_global_brightness(MAX6954h* io, int i){
    write_reg(io, rGLOBAL_INTENSITY, i );
}

void MAX6954_disable_decode_mode(MAX6954h* io){
    write_reg(io, rDECODE_MODE, dmNONE_ALL );
}

void MAX6954_enable_decode_mode(MAX6954h* io){
    write_reg(io, rDECODE_MODE, dmHEX_ALL );
}

void MAX6954_turn_off_all_segments(MAX6954h* io){
    for (RegAdress i = rDIG0; i < rDIG7A ; ++i)
        write_reg(io, i, 0);
}

static
void write_plain(MAX6954h* io, const char str[]){
    RegAdress lim = io->digits;
    assert(lim <= 16);
    lim = (lim-1)&0xf;
    for (RegAdress i = 0; i < lim ; ++i)
        write_reg(io, rDIG0P0+i, str[i]);
}

static
void write_color_pair(MAX6954h* io, const char str[]){
    RegAdress lim = io->digits;
    assert(lim <= 4);
    lim = (lim-1)&3;
    for (RegAdress i = 0; i < lim ; ++i){
        write_reg(io, rDIG0P0+i*2,   str[i]);
        write_reg(io, rDIG0P0+i*2+1, str[i]);
    }
}

static
void write_color_a(MAX6954h* io, const char str[]){
    RegAdress lim = io->digits;
    assert(lim <= 8);
    lim = (lim-1)&7;
    for (RegAdress i = 0; i < lim ; ++i){
        write_reg(io, rDIG0P0+i, str[i]);
        write_reg(io, rDIG0AP0+i, str[i]);
    }
}


void MAX6954_write_raw(MAX6954h* io, const char x[]){
    if (io->color_mode == 0)
        write_plain(io, x);
    else if(io->color_mode == MAX6954_sCOLORED_PAIR)
        write_color_pair(io, x);
    else if (io->color_mode == MAX6954_sCOLORED_A)
        write_color_a(io, x);
}

#include <ctype.h>

enum Dot7SEG{dp = 0x80 ,
    da = 0x40, db = 0x20, dc = 0x10,
    dd = 8, de = 4, df = 2, dg = 1,
};

static
unsigned raw_encode( char c) {
    if (isspace(c))
        return 0;
    switch (c) {
        case '.' :
        case ',' : return dp;

        case '~' : return da;
        case '-' : return dg;
        case '_' : return dd;
        case '=' : return da|dg;
        case '\'' : return db;
        case '(' :
        case '{' :
        case '[' : return da|      dd|de|df;
        case ')' :
        case '}' :
        case ']' : return da|db|dc|dd;
        case '0' : return da|db|dc|dd|de|df;
        case '1' : return    db|dc;
        case '2' : return da|db   |dd|de   |dg;
        case '3' : return da|db|dc|dd      |dg;
        case '4' : return    db|dc|      df|dg;
        case '5' : return da|   dc|dd|   df|dg;
        case '6' : return da|   dc|dd|de|df|dg;
        case '7' : return da|db|dc;
        case '8' : return da|db|dc|dd|de|df|dg;
        case '9' : return da|db|dc|dd|   df|dg;
        case 'a' :
        case 'A' : return da|db|dc|   de|df|dg;
        case 'b' :
        case 'B' : return       dc|dd|de|df|dg;
        case 'c' :
        case 'C' : return da|      dd|de|df;
        case 'd' :
        case 'D' : return    db|dc|dd|de|   dg;
        case 'e' :
        case 'E' : return da|      dd|de|df|dg;
        case 'f' :
        case 'F' : return da|         de|df|dg;
        case 'p' :
        case 'P' : return da|db|      de|df|dg;
        default : return 0;
    }
}


void MAX6954_write_string(MAX6954h* io, const char x[]){
    char sraw[16];
    char* pr = sraw;
    const char* ps = x;
    RegAdress lim = io->digits;

    // конвертирую строку символическую в raw матриц индикатора
    for (unsigned i = 0; i < lim; ){
        char c = *ps++;
        if (c == '\0') break;
        char r = raw_encode(c);

        if (i > 0)
        if ((c ==',') || (c =='.')){
                pr[0] |= r;
                continue;
        }

        *pr++ = r;
        ++i;
    }

    write_reg(io, rDECODE_MODE, dmNONE_ALL );
    MAX6954_write_raw(io, (char*)sraw );
}



#include <stdio.h>
bool MAX6954_write_int(MAX6954h* io, long x){
    char str[24];
    int len = snprintf(str, sizeof(str), "%ld", x);
    if (len <= 0)
        return false;
    MAX6954_write_string(io, str);
    return true;
}

bool MAX6954_write_float(MAX6954h* io, float x){
    char str[24];
    int len = snprintf(str, sizeof(str), "%lg", x);
    if (len <= 0)
        return false;
    MAX6954_write_string(io, str);
    return true;
}

void MAX6954_blink_disable(MAX6954h* io){
    RegData tmp = read_reg(io, rCTRL);
    write_reg(io, rCTRL, (tmp & ~cfBLINK_ENA) );
}

void MAX6954_blink_enable(MAX6954h* io){
    RegData tmp = read_reg(io, rCTRL);
    write_reg(io, rCTRL, (tmp | cfBLINK_ENA) );
}



//----------------------------------------------------------
//     HardWare specific part
// provide:
//  MAX6954_BLINK_TIME_SECOND - значение таймера для периода мелькания 1сек

enum MAX6954_BLINK_Def{
    // минимальные перид
    MAX6954_BLINK_LOW_01SEC = 2,
    // максимальный перид
    MAX6954_BLINK_TOP_01SEC = 20,
};

#include <hw.h>
#include <mcu_system.h>

#ifdef MAX6954_BLINK_TIME_SECOND
void MAX6954_blink_assign(MAX6954h* io, TIM_TypeDef* tim){
    io->blink_tim = tim;
}

void MAX6954_blink_rate_01s(MAX6954h* io, unsigned rate_01s){
    assert(io->blink_tim != NULL);

    unsigned rate = (rate_01s * MAX6954_BLINK_TIME_SECOND / 10);

    const unsigned rate_lo  = MAX6954_BLINK_TIME_SECOND*4;
    const unsigned rate_top = (MAX6954_BLINK_TIME_SECOND+1)/2;
    if (rate > rate_lo)
        rate = rate_lo;
    else if (rate < rate_top)
        rate = rate_top;

    TIM_SetAutoReload(io->blink_tim, rate);
}
#endif

// активировать и настроить обмен с драйвером
void MAX6954_begin(MAX6954h* io){
    SPI_Enable(io->io);
    SPI_SetTransferBitOrder(io->io, SPI_MSB_FIRST);
    SPI_SetDataWidth(io->io, SPI_DATAWIDTH_8BIT);
    SPI_SetDataMode(io->io, SPI_DATA_MODE0);

    // SPI Speed = 1MHz
    // SPI.setClockDivider(SPI_CLOCK_DIV8);  // 8MHz Arduino
    SPI_SetBaudRatePrescaler(io->io, SPI_BAUDRATEPRESCALER_DIV4); // 16MHz Arduino
    // SPI_CLOCK_DIV possibilities 2,4,8,16,32,64,128
}

static
void SPI_send(SSP_TypeDef*   io, unsigned x){
    while (!SPI_Is_TXE(io));
    SPI_TransmitData8(io, x);
    while (!SPI_Is_BSY(io));
}

static
unsigned SPI_transfer8(SSP_TypeDef*   io, unsigned x){
    while (!SPI_Is_TXE(io));
    SPI_TransmitData8(io, x);
    while (!SPI_Is_BSY(io));
    return SPI_ReceiveData8(io);
}

static
void write_reg(MAX6954h* io, RegAdress adr, RegData x){
    GPIO_ResetBits(io->io_cs, io->cs_pin);

    SPI_send(io->io, adr | rWR);
    SPI_send(io->io, 0x00);

    GPIO_SetBits(io->io_cs, io->cs_pin);

    clock_delay_nsec(MAX6954_CS_HOLDNS);
}

static
RegData read_reg(MAX6954h* io, RegAdress adr){
    GPIO_ResetBits(io->io_cs, io->cs_pin);

    SPI_send(io->io, adr | rRD);
    SPI_send(io->io, 0x00);

    GPIO_SetBits(io->io_cs, io->cs_pin);

    clock_delay_nsec(MAX6954_CS_HOLDNS);

    GPIO_ResetBits(io->io_cs, io->cs_pin);
    RegData x = SPI_transfer8(io->io, adr);
    GPIO_SetBits(io->io_cs, io->cs_pin);

    return x;
}

//------------------------------------------------------
void MAX6954_port_set_mode(MAX6954h* io
                        , MAX6954_KeyMode keys
                        , MAX6954_PortMode pins)
{
    write_reg(io, rPORT_CFG, (pins&portDIR_Msk)
                        | (keys&MAX6954_KEYS_Msk)<<portKEYS_LINES_Pos
              );
}

bool      MAX6954_is_some_keys(MAX6954h* io){
    return (MAX6954_gpio(io) & MAX6954_KEYSIRQ) != 0;
}

bool      MAX6954_check_keys(MAX6954h* io){
    MAX6954_begin(io);
    return MAX6954_is_some_keys(io);
}

uint32_t  MAX6954_keys_1line(MAX6954h* io){
    return read_reg(io, rKEYA_DEB);
}

uint32_t  MAX6954_keys_2line(MAX6954h* io){
    uint32_t res = 0;
    res = read_reg(io, rKEYA_DEB) & 0xff;
    res |= (read_reg(io, rKEYB_DEB) & 0xff) << 8;
    return res;
}

uint32_t  MAX6954_keys_3line(MAX6954h* io){
    uint32_t res = 0;
    res = read_reg(io, rKEYA_DEB) & 0xff;
    res |= (read_reg(io, rKEYB_DEB) & 0xff) << 8;
    res |= (read_reg(io, rKEYC_DEB) & 0xff) << 16;
    return res;
}

uint32_t  MAX6954_keys_4line(MAX6954h* io)
{
    uint32_t res = 0;
    res = read_reg(io, rKEYA_DEB) & 0xff;
    res |= (read_reg(io, rKEYB_DEB) & 0xff) << 8;
    res |= (read_reg(io, rKEYC_DEB) & 0xff) << 16;
    res |= (read_reg(io, rKEYD_DEB) & 0xff) << 24;
    return res;
}
uint32_t  MAX6954_keys(MAX6954h* io){
    return MAX6954_keys_4line(io);
}

MAX6954_PortState MAX6954_gpio(MAX6954h* io){
    return read_reg(io, rGPIO);
}

void MAX6954_gpio_set(MAX6954h* io, MAX6954_PortState x ){
    write_reg(io, rGPIO, x);
}

//void MAX6954_all_segment_test(MAX6954h* io)

