#include "util.h"

unsigned int vcc=0;
unsigned char n=0;
unsigned int tmOut=0;
const char* s;
char newIndex=0;    //flag that dice have changed
char udFlag=0;      //display update needed
displayPair_t current;
settings_t settingsMode=0;    //0 for normal ops, 1+ for settings
char lhsResult=0;
char rhsResult=0;
char rollFromSleep=0;
char OLEDisDim=0;

//in order or graphicsDisplay_t (number, pips, card, coin, null)
const char cardXleft[]={2,4,2,4,2};
const char cardXcentre[]={2+32,4+32,2+32,4+32,2+32};
const char cardXright[]={2+64,4+64,2+64,4+64,2+64};

const char settingsNames[][12]={
    "???       ",   //not used, error
    "TIMEOUT   ",
    "BRIGHTNESS",
#ifdef ALLOW_OLED_X_SETTING
    "OLED X    ",
#endif //ALLOW_OLED_X_SETTING
    "SHAKE TO  ",
    "MEM TEST  ",
    "USE NOISE ",
    "ROLL#     " //appended with index
};

__eeprom displayPair_t eePairs[EE_PAIRS]={
    {.lhs={.disp=DISP_COIN,.colour=BLACK,.roll=2,.x=L_COIN_X},.rhs={.disp=DISP_COIN,.colour=WHITE,.roll=2,.x=R_COIN_X}},  //two-up
    {.lhs={.disp=DISP_PIPS,.colour=BLACK,.roll=6,.x=L_PIPS_X},.rhs={.disp=DISP_PIPS,.colour=WHITE,.roll=6,.x=R_PIPS_X}},   //2d6
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=4,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=4,.x=R_NUM_X}}, //2d4
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=8,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=8,.x=R_NUM_X}}, //2d8
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=10,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=10,.x=R_NUM_X}}, //2d10
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=12,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=12,.x=R_NUM_X}}, //2d12
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=20,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=20,.x=R_NUM_X}}, //2d20
    {.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=100,.x=L_NUM_X},.rhs={.disp=DISP_NUMBER,.colour=WHITE,.roll=100,.x=R_NUM_X}}, //2d100
    {.lhs={.disp=DISP_CARD,.colour=BLACK,.roll=52,.x=L_CARD_X},.rhs={.disp=DISP_CARD,.colour=WHITE,.roll=52,.x=R_CARD_X}}, //2x52 card pick
    {.lhs={.disp=DISP_CARD,.colour=BLACK,.roll=54,.x=L_CARD_X},.rhs={.disp=DISP_CARD,.colour=WHITE,.roll=54,.x=R_CARD_X}}, //2x54 card pick
    //some templates
    //{.lhs={.disp=DISP_NULL},.rhs={.disp=DISP_NULL}}, //empty
    //{.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=4,.x=34},.rhs={.disp=DISP_NULL}}, //d4
    //{.lhs={.disp=DISP_PIPS,.colour=BLACK,.roll=6,.x=36},.rhs={.disp=DISP_NULL}},   //d6
    //{.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=8,.x=34},.rhs={.disp=DISP_NULL}}, //d8
    //{.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=10,.x=34},.rhs={.disp=DISP_NULL}}, //d10
    //{.lhs={.disp=DISP_NUMBER,.colour=BLACK,.roll=12,.x=34},.rhs={.disp=DISP_NULL}}, //d12        
};

__eeprom unsigned char timeOut=10;
__eeprom unsigned char oledBrightness=100;  //double the % value (ie 50%)
__eeprom unsigned char useMEM=1;    //use MEM hardware
__eeprom char currentIndex=0; //rollTemplates up to TEMPLATE_COUNT then eePairs up to EE_PAIRS
__eeprom char shakeToWake=1; //allow vibration switch to wake up from sleep
__eeprom unsigned long lfsrEEPROM=LFSR_INIT;    //can't be zero or it will lock up

void drawBorderCurrent(void){
    drawBorderGraphics(&current.lhs);
    s=rollName(current.lhs);    
    OLEDcol=current.lhs.x+TEXT_OFFSET;
    OLEDpage=4;
    OLEDchararray(s,smallFont);
    drawBorderGraphics(&current.rhs);
    s=rollName(current.rhs);
    OLEDcol=current.rhs.x+TEXT_OFFSET;
    OLEDpage=4;
    OLEDchararray(s,smallFont);
}

char buttonHoldRepeat(void){    //monitor button for hold and report repeats as needed
    static char last=0; //nothing
    static char delayDone=0;
    char i;
    char next=0;
    for(i=1;i<=6;i++){
        if(buttonState(i)){
            if(next){ //more than one button, don't report anything
                last=0;
                delayDone=0;
                return 0;
            }
            next=i;
        }
    }
    if(next){
        if(last){   //still held down after 1st return
            if(delayDone){
                __delay_ms(REPEAT_INTERVAL);
            }else{
                __delay_ms(REPEAT_DELAY);
                delayDone=1;
            }
            if(buttonState(next)){
                return next;
            }
        }else{      //1st return
            last=1;
            delayDone=0;
            return next;
        }
    }
    last=0;
    delayDone=0;
    return 0;                
}

char checkButton(char n){
    if(buttonState(n)){
        while(buttonState(n)){}
        __delay_ms(10); //debouncing
        return 1;
    }
    return 0;    
}

char buttonState(char n){
    switch(n){
        case 1: return PRESSED(S1); break;
        case 2: return PRESSED(S2); break;
        case 3: return PRESSED(S3); break;
        case 4: return PRESSED(S4); break;
        case 5: return PRESSED(S5); break;
        case 6: return PRESSED(S6); break;
        case 7: return PRESSED(S7); break;
    }
    return 0;
}

void timerReset(void){
    tmOut=((unsigned int)(timeOut))*T0_FREQ-1;    //-1 avoids flickering on digit
}

void settingsTitle(void){
    OLEDclear(0);
    OLEDpage=0;                            
    OLEDcol=0;
    OLEDchararray("SETTINGS",arial);
    OLEDpage=2;                            
    OLEDcol=0;
    if(settingsMode>=SETTINGS_DUMMY){   //rolls
        OLEDchararray(settingsNames[SETTINGS_DUMMY],arial);        
        OLEDcol=45; //after "ROLL#"
        OLEDscanshort((unsigned int)(settingsMode-SETTINGS_DUMMY+1));
        OLEDlzb(8);
        if(OLEDbuf[8]!=OLED_BLANK_CHAR){OLEDchar(OLEDbuf[8],arial);}
        OLEDchar(OLEDbuf[9],arial);
    }else{  //config
        OLEDchararray(settingsNames[settingsMode],arial);        
    }    
}

void runMEMtest(void){  //runs test and displays results
    OLEDpage=0;                            
    OLEDcol=0;
    OLEDchararray("READY  ",arial);    
    OLEDpage=2;     
    OLEDcol=0;  
    OLEDchararray("UP:RUN   DOWN:RESET",smallFont);
    OLEDpage=3;     
    OLEDcol=0;  
    OLEDchararray("LEFT:FINISH",smallFont);    
    showDtest();
    while(1){       //until exit via return
        if(checkButton(LEFT_BUTTON)){return;}
        if(checkButton(UP_BUTTON)){
            OLEDpage=0;                            
            OLEDcol=0;
            OLEDchararray("RUNNING",arial);
            runDtest();
            checkState();
            showDtest();
            OLEDpage=0;                            
            OLEDcol=0;
            OLEDchararray("DONE   ",arial);
        }
        if(checkButton(DOWN_BUTTON)){
            oneD[0]=0;oneD[1]=0;
            twoD[0][0]=0;twoD[0][1]=0;
            twoD[1][0]=0;twoD[1][1]=0;
            checkState();   //clear status variables
            showDtest();
        }        
    }
}

void showDtest(void){
    OLEDpage=4;     
    OLEDcol=0;  
    OLEDchararray("    ->0   ->1     T",smallFont);
    OLEDpage=5;     
    OLEDcol=0;  
    OLEDchar('0',smallFont);
    OLEDscanshort((unsigned int)twoD[0][0]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);
    OLEDscanshort((unsigned int)twoD[0][1]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);
    OLEDscanshort((unsigned int)oneD[0]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);
    OLEDpage=6;     
    OLEDcol=0;  
    OLEDchar('1',smallFont);
    OLEDscanshort((unsigned int)twoD[1][0]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);
    OLEDscanshort((unsigned int)twoD[1][1]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);
    OLEDscanshort((unsigned int)oneD[1]);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[4],smallFont);    
    OLEDpage=7;     
    OLEDcol=0;  
    OLEDchararray("FAIR:",smallFont);
    OLEDscanshort(fairnessHealth);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[7],smallFont);
    OLEDchararray("% CORR:",smallFont);
    OLEDscanshort(correlationHealth);
    OLEDlzb(8);
    OLEDchararray(&OLEDbuf[7],smallFont);
    OLEDchar('%',smallFont);    
}

void setRoll(char n){     //configure roll eePairs[n]
    char p=0;   //for item to edit disp, roll, colour for left then right
    char i=0;    //increment for menu    
    char j=0;   //repeat
    char d=1;   //demo value for display
    rollDisplay_t sRoll;    //local used by setRoll
    sRoll=eePairs[n].lhs;//init value
    udFlag=3;   //force draw
    while(1){       //until exit via return
        //check buttons
        if(checkButton(OK_BUTTON)){return;}
        if(checkButton(LEFT_BUTTON)){if(p>0){p=p-1;}else{p=5;}udFlag=3;}
        if(checkButton(RIGHT_BUTTON)){if(p<5){p=p+1;}else{p=0;}udFlag=3;}
        if((p==1)||(p==4)){ //allow hold for fast scroll
            j=buttonHoldRepeat();
            if(j==UP_BUTTON){i=UP_BUTTON;udFlag=2;}
            if(j==DOWN_BUTTON){i=DOWN_BUTTON;udFlag=2;}
        }else{
            if(checkButton(UP_BUTTON)){i=UP_BUTTON;udFlag=3;}
            if(checkButton(DOWN_BUTTON)){i=DOWN_BUTTON;udFlag=3;}                        
        }
        //update
        switch(p){
            case 0: //LHS type
                if(i==UP_BUTTON){
                    if(eePairs[n].lhs.disp==DISP_COIN){
                        eePairs[n].lhs.disp=DISP_NUMBER;
                    }else{
                        eePairs[n].lhs.disp++;
                    }
                }
                if(i==DOWN_BUTTON){
                    if(eePairs[n].lhs.disp==DISP_NUMBER){
                        eePairs[n].lhs.disp=DISP_COIN;
                    }else{
                        eePairs[n].lhs.disp--;
                    }
                }
                break;
            case 1: //LHS roll, validated below
                if(i==UP_BUTTON){eePairs[n].lhs.roll++;}
                if(i==DOWN_BUTTON){eePairs[n].lhs.roll--;}
                break;
            case 2: //LHS colour, toggle
                if(i){
                    if(eePairs[n].lhs.colour==BLACK){
                        eePairs[n].lhs.colour=WHITE;
                    }else{
                        eePairs[n].lhs.colour=BLACK;
                    }
                }
                break;
            case 3: //RHS type
                if(i==UP_BUTTON){
                    if(eePairs[n].rhs.disp==DISP_NULL){ //allow to set to null
                        eePairs[n].rhs.disp=DISP_NUMBER;
                    }else{
                        eePairs[n].rhs.disp++;
                    }
                }
                if(i==DOWN_BUTTON){
                    if(eePairs[n].rhs.disp==DISP_NUMBER){ //allow to set to null
                        eePairs[n].rhs.disp=DISP_NULL;
                    }else{
                        eePairs[n].rhs.disp--;
                    }
                }
                break;
            case 4: //RHS roll, validated below
                if(i==UP_BUTTON){eePairs[n].rhs.roll++;}
                if(i==DOWN_BUTTON){eePairs[n].rhs.roll--;}
                break;
            case 5: //RHS colour
                if(i){
                    if(eePairs[n].rhs.colour==BLACK){
                        eePairs[n].rhs.colour=WHITE;
                    }else{
                        eePairs[n].rhs.colour=BLACK;
                    }
                }
                break;
        }
        checkMinMax(n);
        i=0;    //assume processed
        //display
        if(udFlag>2){  //avoid flickering
            udFlag=2;
            OLEDclear(0);
            if(p<3){
                sRoll=eePairs[n].lhs;
            }else{
                sRoll=eePairs[n].rhs;
            }
            sRoll.x=66; //fix for display purposes
            d=sRoll.roll;   //use for display
            drawBorderGraphics(&sRoll);
        }
        if(udFlag>1){  //avoid flickering
            udFlag=1;
            OLEDpage=0;        
            OLEDcol=0;
            OLEDchararray("ROLL#",arial);
            OLEDscanshort(n+1);        
            OLEDlzb(8);
            if(OLEDbuf[8]!=OLED_BLANK_CHAR){OLEDchar(OLEDbuf[8],arial);}
            OLEDchar(OLEDbuf[9],arial);
            OLEDchararray(" OK=DONE",smallFont);
            OLEDpage=2;        
            OLEDcol=0;
            if(p<3){
                OLEDchararray(" LEFT ",arial);
                sRoll=eePairs[n].lhs;
            }else{
                OLEDchararray(" RIGHT",arial);            
                sRoll=eePairs[n].rhs;
            }
            sRoll.x=66; //fix for display purposes
            d=sRoll.roll;   //use for display
            OLEDpage=4;        
            OLEDcol=0;
            if((p==0)|(p==3)){
                OLEDchararray("TYPE:  ",arial);
                OLEDpage=6;        
                OLEDcol=0;
                OLEDchararray(dispNames[sRoll.disp],arial);
            }else if((p==1)|(p==4)){
                OLEDchararray("ROLL:  ",arial);
                OLEDpage=6;        
                OLEDcol=0;
                OLEDscanshort(sRoll.roll);        
                OLEDlzb(8);
                OLEDchararray(&OLEDbuf[6],arial);
                OLEDchararray("   ",arial);
            }else{  //2,5
                OLEDchararray("COLOUR:",arial);
                OLEDpage=6;        
                OLEDcol=0;
                if(sRoll.colour==WHITE){
                    OLEDchararray(" WHITE ",arial);
                }else{
                    OLEDchararray(" BLACK ",arial);                    
                }
            }
            drawGraphics(&sRoll,d);                        
        }
    }    
}

void checkMinMax(char n){       //validate min/max roll, also adjust x
    if(eePairs[n].lhs.roll>rollMax[eePairs[n].lhs.disp]){
        eePairs[n].lhs.roll=rollMax[eePairs[n].lhs.disp];
    }
    if(eePairs[n].lhs.roll<rollMin[eePairs[n].lhs.disp]){
        eePairs[n].lhs.roll=rollMin[eePairs[n].lhs.disp];
    }
    if(eePairs[n].rhs.roll>rollMax[eePairs[n].rhs.disp]){
        eePairs[n].rhs.roll=rollMax[eePairs[n].rhs.disp];
    }
    if(eePairs[n].rhs.roll<rollMin[eePairs[n].rhs.disp]){
        eePairs[n].rhs.roll=rollMin[eePairs[n].rhs.disp];
    }       
    //adjust x:
    if(eePairs[n].rhs.disp==DISP_NULL){ //only LHS is valid, centre it
        eePairs[n].lhs.x=cardXcentre[eePairs[n].lhs.disp];
    }else{  //both present, adjust to suit
        eePairs[n].lhs.x=cardXleft[eePairs[n].lhs.disp];
        eePairs[n].rhs.x=cardXright[eePairs[n].rhs.disp];
    }
}

char getSelectedRoll(char n){   //use getRoll/getRollLFSR depending on useMEM
    if(useMEM){
        return getRoll(n);
    }else{
        return getRollLFSR(n);
    }
}

char getSelectedBit(void){ //use entropy/LFSR depending on useMEM
    if(useMEM){
        return getRNDbit();
    }else{
        return getLFSRbit();
    }    
}

void startUp(void){  //used at boot and also restart from sleep
    OSCFRQ=HFOSC_FRQ_16;
    OSCCON1=CLOCK_HFINTOSC; //NOSC:NDIV
    //Peripheral module disable (0=enabled),all are enabled at boot, re-enabling is same as POR
                //  7      6      5      4      3      2       1        0
    PMD0=0x06;  //TMR0MD CLKRMD IOCMD  ACTMD   SYSCMD  SCANMD  CRCMD    NVMMD       //need T0, IOC, NVM for eeprom
    PMD1=0xFF;  //PWM1MD CCP2MD CCP1MD ------  TMR4MD  TMR2MD  TMR3MD   TMR1MD
    PMD2=0xFF;  //CLC3MD CLC2MD CLC1MD CWG1MD  NCO1MD  ------  ------   PWM2MD
    PMD3=0xDD;  //CM2MD  CM1MD  FVRMD  MSSP2MD MSSP1MD UART2MD UART1MD  CLC4MD      //need FVR, UART1
    PMD4=0xFE;  //------ ------ ------ ZCDMD   ------  DAC2MD  DAC1MD   ADCMD       //need ADC
    initADC();
    I2Cinit();
    OLEDinit();    
    uartInit(BRG_115200);
    //uartInit(BRG_9600);
    OLEDclear(0);
    OLEDbrightness(oledBrightness);
    if(useMEM){
        setRND(1);  //turn on
        getEntropy();
    }    
    t0init();
    timerReset();
}

void lowPowerWait(void){    //doze while waiting for things to happen
    char done=0;
    CPUDOZE=CPU_DOZE_ROI;
    while((udFlag==0)&&(done==0)){  //udFlag is set in interrupt    
        if(buttonState(BACK_BUTTON)) {done=1;}
        if(buttonState(OK_BUTTON))   {done=1;}
        if(buttonState(UP_BUTTON))   {done=1;}
        if(buttonState(LEFT_BUTTON)) {done=1;}
        if(buttonState(RIGHT_BUTTON)){done=1;}
        if(buttonState(DOWN_BUTTON)) {done=1;}
        if(buttonState(SHAKE_BUTTON)){done=1;}
    }    
    CPUDOZE=CPU_DOZE_OFF;
}

void shutDown(void){   //before sleep
    lfsrEEPROM=lfsr_data32; //save to EEPROM    
    uartDeInit();   //off
    setRND(0);  //turn off
    I2Cdeinit(); //no need to deinit OLED
    deInitADC();    
    //timer runs off OSC, so stops anyway
                //  7      6      5      4      3      2       1        0
    PMD0=0xDF;  //TMR0MD CLKRMD IOCMD  ACTMD   SYSCMD  SCANMD  CRCMD    NVMMD       //need IOC
    PMD1=0xFF;  //PWM1MD CCP2MD CCP1MD ------  TMR4MD  TMR2MD  TMR3MD   TMR1MD
    PMD2=0xFF;  //CLC3MD CLC2MD CLC1MD CWG1MD  NCO1MD  ------  ------   PWM2MD
    PMD3=0xFF;  //CM2MD  CM1MD  FVRMD  MSSP2MD MSSP1MD UART2MD UART1MD  CLC4MD
    PMD4=0xFF;  //------ ------ ------ ZCDMD   ------  DAC2MD  DAC1MD   ADCMD 
    OSCCON1=CLOCK_LFINTOSC; //NOSC:NDIV
}

void loadRoll(void){    //load current from EEPROM/templates
    if(currentIndex<TEMPLATE_COUNT){
        current.lhs=rollTemplates[currentIndex];
        current.rhs=emptyTemplate;                
    }else{
        current.lhs=eePairs[currentIndex-TEMPLATE_COUNT].lhs;   //compiler can't copy struct of structs!
        current.rhs=eePairs[currentIndex-TEMPLATE_COUNT].rhs;
    }    
}

void sleepAndWakeup(void){  //set up wake source, sleep, recover
    IOCAF=0;
    IOCBF=0;
    IOCCF=0;    //ensure no flags set whatsoever
    //iocxfbits.S1=0;
    //iocxfbits.S2=0;
    //iocxfbits.S3=0;
    //iocxfbits.S4=0;
    //iocxfbits.S5=0;
    //iocxfbits.S6=0;
    iocxpbits.S1=1;
    iocxpbits.S2=1;
    iocxpbits.S3=1;
    iocxpbits.S4=1;
    iocxpbits.S5=1;
    iocxpbits.S6=1;
    iocxnbits.S1=1;
    iocxnbits.S2=1;
    iocxnbits.S3=1;
    iocxnbits.S4=1;
    iocxnbits.S5=1;
    iocxnbits.S6=1;
    //note: ignoring S7 vibration switch unless otpion set
    if(shakeToWake){
        iocxpbits.S7=1;
        iocxnbits.S7=1;        
    }else{
        iocxpbits.S7=0;
        iocxnbits.S7=0;                
    }
    CPUDOZE=CPU_DOZE_OFF;   //idle, doze off
    PIE0bits.IOCIE=1;
    SLEEP();
    NOP();
    PIE0bits.IOCIE=0;
}
