/*
This is the modified version of [calculator by MWWorks](https://github.com/MWWorks/mw_calc_numpad/blob/master/calc.c). Below is the quote from [MWWorks](https://github.com/MWWorks).

   Calculator for QMK-based keyboard by MWWorks, https://mwworks.uk
   This is free, usual disclaimers, don't use it to calculate megaton yields, surgery plans, etc

   I did not plan to reinvent the wheel for this - I figured surely somebody somewhere has working calculator code?
   Found lots but none that actually work like you expect a calculator to, hence DIYing it

   As such, this is probably a bit janky, especially as I am a bit of a hack at C
   Seems to be working well, with occasional glitchs, solved by clearing it
   And some occasional floating-point issues - eg get a long decimal rather than the whole number you were expecting
   Feel free to fix it! I think it needs to detect the precision of the two operands and then figure out what the precision of the result should be

*/
#include "rubi.h"

static uint8_t calc_current_operand = 0;
static char calc_operand_0[CALC_DIGITS+1] = "";
static char calc_operand_1[CALC_DIGITS+1] = "";
char calc_result[CALC_DIGITS+1] = "";
static char calc_status[CALC_DIGITS+1] = "";
static char calc_operator = ' ';
static bool calc_reset = false;


void calcBegin(void){
}

//update display
void calcUpdate(void){
    if (calc_display_lines == 2) {
        if((calc_current_operand == 1) || (calc_reset)){
            strcpy(calc_status, calc_operand_0);
            if((strlen(calc_operand_0)>0) || (strlen(calc_operand_1)>0)){
                uint8_t len = strlen(calc_status);
                if (!(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')) {
                    calc_status[len] = calc_operator;
                }
                calc_status[len+1] = 0;
                if(calc_reset
                        && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){
                    strncat(calc_status, calc_operand_1, CALC_DIGITS-strlen(calc_status));
                    calc_operator = ' ';
                }
            }
            strcpy(calc_status_display, calc_status);
        }
    } else if (calc_display_lines == 1) {
        if(calc_reset
                && !(calc_operator == 's' || calc_operator == 'r' || calc_operator == 'n')){
            calc_operator = ' ';
        }
    }
    calc_operator_display = calc_operator;
    strcpy(calc_result_display, calc_result);
}

//perform calculation on the 2 operands
void calcOperands(void){
    float result = 0;
    switch (calc_operator){

        //standard operators
        case '+':
            result = strtod(calc_operand_0, NULL) + strtod(calc_operand_1, NULL);
            break;

        case '-':
            result = strtod(calc_operand_0, NULL) - strtod(calc_operand_1, NULL);
            break;

        case '/':
            result = strtod(calc_operand_0, NULL) / strtod(calc_operand_1, NULL);
            break;

        case '*':
            result = strtod(calc_operand_0, NULL) * strtod(calc_operand_1, NULL);
            break;

            //single operand operators - these are all in 2
        case 's':
            result = sqrt(strtod(calc_operand_0, NULL));
            break;

        case 'r':
            result = 1/(strtod(calc_operand_0, NULL));
            break;

    }

    //now convert the float result into a string
    //we know the total string size but we need to find the size of the integer component to know how much we have for decimals
    uint8_t magnitude = ceil(log10(result));
    uint8_t max_decimals = CALC_DIGITS-magnitude-1;
    //but max it at 7 because that seems the useful limit of our floats
    if(max_decimals>7){
        max_decimals = 7;
    }
    dtostrf(result, CALC_DIGITS, max_decimals, calc_result);

    //now to clean up the result - we need it clean as it may be the input of next calculation
    //this seems a lot of code to format this string :| note that this c doesn't support float in sprintf
    uint8_t i;

    //first find if theres a dot
    uint8_t dotpos = CALC_DIGITS+1;
    for(i=0; i<strlen(calc_result); i++){
        if(calc_result[i] == '.'){
            dotpos = i;
            break;
        }
    }

    //if there is, work back to it and remove trailing 0 or .
    if(dotpos>=0){
        for(i=strlen(calc_result)-1; i>=dotpos; i--){
            if((calc_result[i] == '0') || (calc_result[i] == '.')){
                calc_result[i] = 0;
            }else{
                break;
            }
        }
    }

    //now find how many leading spaces
    uint8_t spaces = 0;
    for(i=0; i<strlen(calc_result); i++){
        if(calc_result[i] == ' '){
            spaces++;
        }else{
            break;
        }
    }

    //and shift the string
    for(i=0; i<strlen(calc_result)-spaces; i++){
        calc_result[i] = calc_result[i+spaces];
    }
    calc_result[strlen(calc_result)-spaces] = 0;

    calcUpdate();
    //the result is available as the first operand for another calculation
    strcpy(calc_operand_0, calc_result);
    calc_operand_1[0] = 0;

}

void calcInput(char input){
    char *operand = calc_operand_0;
    if(calc_current_operand == 1){
        operand = calc_operand_1;
    }
    uint8_t len = strlen(operand);

    if(
            ((input >= 48) && (input <= 57)) ||
            (input == '.')
      ){
        //if this is following an equals, then we start from scratch as if new calculation
        if(calc_reset == true){
            calc_reset = false;
            calc_current_operand = 0;
            calc_operand_0[0] = 0;
            calc_operand_1[0] = 0;
            operand = calc_operand_0;
            len = 0;
        }

        if(len<CALC_DIGITS){
            operand[len] = input;
            operand[len+1] = 0;
            strcpy(calc_result, operand);
            calcUpdate();
        }

        //special input to backspace
    }else if(input == 'x'){
        operand[len-1] = 0;
        strcpy(calc_result, operand);
        calcUpdate();

        //clear
    }else if(input == 'c'){
        operand[0] = 0;
        calc_operand_0[0] = 0;
        calc_operand_1[0] = 0;
        calc_operator = ' ';
        calc_reset = true;
        strcpy(calc_result, operand);
        calcUpdate();

        //special input switch neg/pos
    }else if((input == 'n') && (len>0)){
        uint8_t i;

        if(operand[0] == '-'){
            for(i=1; i<=len; i++){
                operand[i-1] = operand[i];
            }
        }else if(len<CALC_DIGITS){
            for(i=0; i<=len; i++){
                operand[len-i+1] = operand[len-i];
            }
            operand[0] = '-';
        }
        calc_operator = input;
        strcpy(calc_result, operand);
        calcUpdate();


        //standard 2 operand operators
    }else if((input == '+') || (input == '-') || (input == '*')  || (input == '/')){

        //get ready for second operand
        if(calc_current_operand == 0){
            calc_operator = input;
            calc_current_operand = 1;
            calcUpdate();

            //we pressed = we now expect a new second operand
        }else if(calc_reset){
            calc_operator = input;
            calc_reset = false;
            calc_operand_1[0] = 0;
            calcUpdate();

        }else {
            //if we use this on the second operand, calculate first, then ready for a second operand again
            if (strlen(calc_operand_1)>0){
                calcOperands();
            }
            calc_operand_1[0] = 0;
            calc_operator = input;
            calcUpdate();
        }


    }else if(input == '='){
        //only accept = if we are on the second operand
        if(calc_current_operand == 1){
            //keep the second operand for a subsequent press of =; but flag to reset if start entry of new operand
            calc_reset = true;
            calcOperands();
        }

        //single operands - square root and reciprocal - needs to operate on 0 so it works after a previous = result
    }else if((input == 's') || (input == 'r')){
        //but maybe we started entering 1
        if(calc_current_operand == 1 && !calc_reset){
            strcpy(calc_operand_0, calc_operand_1);
        }
        calc_current_operand = 1;
        calc_operand_1[0] = 0;
        calc_operator = input;
        calc_reset = true; //simulate another =
        calcOperands();

    }

}