/* 
    abCExprInt.c
        By: Jeremy Rand
 */


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "abCMode.h"

#include "expr/abCExpr.h"


static abCalcExpr *abCalcExprIntParse(abCalcExpr *expr, char *buffer);
static char *abCalcExprIntFormat(abCalcExpr *expr, char *buffer);


static abCalcExprCallbacks gIntCallbacks = {
    abCalcExprIntParse,
    abCalcExprIntFormat
};


void abCalcExprIntInit(void)
{
    abCalcRegisterExprType(abCalcExprTypeInt, &gIntCallbacks);
}


abCalcExpr *abCalcExprIntParse(abCalcExpr *expr, char *buffer)
{
    abCalcModeIntBase base = abCalcModeGetBase();
    abCalcIntType value = 0;
    int len;
    int offset = 1;

    if (buffer == NULL)
        return NULL;

    if (expr == NULL)
        return NULL;

    len = strlen(buffer);
    if (len < 2)
        return NULL;

    if (buffer[0] == '$') {
        base = abCalcModeHexBase;
    } else if ((buffer[0] == '0') &&
               (buffer[1] == 'x')) {
        base = abCalcModeHexBase;
        offset = 2;
    } else if (buffer[0] != '#')
        return NULL;

    switch (base) {
        case abCalcModeBinBase:
            for ( ; offset < len; offset++) {
                value <<= 1;
                switch (buffer[offset]) {
                    case '1':
                        value++;
                        break;

                    case '0':
                        break;
                        
                    default:
                        return NULL;
                }
            }
            break;

        case abCalcModeOctBase:
            for ( ; offset < len; offset++) {
                value <<= 3;
                switch (buffer[offset]) {
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                        value += (buffer[offset] - '0');;
                        break;
                        
                    default:
                        return NULL;
                }
            }
            break;

        case abCalcModeDecBase:
            for ( ; offset < len; offset++) {
                value *= 10;
                switch (buffer[offset]) {
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        value += (buffer[offset] - '0');;
                        break;
                        
                    default:
                        return NULL;
                }
            }
            break;

        case abCalcModeHexBase:
            for ( ; offset < len; offset++) {
                value <<= 4;
                switch (buffer[offset]) {
                    case '0':
                    case '1':
                    case '2':
                    case '3':
                    case '4':
                    case '5':
                    case '6':
                    case '7':
                    case '8':
                    case '9':
                        value += (buffer[offset] - '0');;
                        break;

                    case 'a':
                    case 'A':
                        value += 10;
                        break;

                    case 'b':
                    case 'B':
                        value += 11;
                        break;
                        
                    case 'c':
                    case 'C':
                        value += 12;
                        break;
                        
                    case 'd':
                    case 'D':
                        value += 13;
                        break;
                        
                    case 'e':
                    case 'E':
                        value += 14;
                        break;
                        
                    case 'f':
                    case 'F':
                        value += 15;
                        break;
                        
                    default:
                        return NULL;
                }
            }
            break;

        default:
            return NULL;
    }

    expr->type = abCalcExprTypeInt;
    expr->u.integer = value;
    return expr;
}


char *abCalcExprIntFormat(abCalcExpr *expr, char *buffer)
{
    abCalcModeIntBase base = abCalcModeGetBase();
    int width = abCalcModeGetIntWidth();
    abCalcIntType value;
    char *ptr;
    int gotFirstOne;
    int i;

    if (expr == NULL)
        return NULL;

    if (buffer == NULL)
        return NULL;

    if (expr->type != abCalcExprTypeInt)
        return NULL;

    value = expr->u.integer;

    if (width < AB_CALC_EXPR_MAX_INT_WIDTH) {
        value &= ((1l << width) - 1);
    }

    switch (base) {
        case abCalcModeBinBase:
		    gotFirstOne = 0;
            ptr = buffer;
            *ptr = '#';
            ptr++;
		
		    for (i = width - 1; i >= 0; i--) {
                if ((value >> i) & 1) {
                    *ptr = '1';
                    ptr++;
                    gotFirstOne = 1;
                } else {
                    if (gotFirstOne) {
                        *ptr = '0';
                        ptr++;
                    }
                }
            }
		    if (!gotFirstOne) {
                *ptr = '0';
                ptr++;
            }
            *ptr = ' ';
            ptr++;
            *ptr = 'b';
            ptr++;
            *ptr = '\0';
            break;
            
        case abCalcModeOctBase:
            sprintf(buffer, "#%lo o", value);
            break;

        case abCalcModeDecBase:
            sprintf(buffer, "#%lu d", value);
            break;

        case abCalcModeHexBase:
            sprintf(buffer, "#%lX h", value);
            break;

        default:
            return NULL;
    }

    return buffer;
}


void abCalcExprIntSet(abCalcExpr *expr, abCalcIntType value)
{
    int width;

    if (expr == NULL)
        return;

    width = abCalcModeGetIntWidth();

    expr->type = abCalcExprTypeInt;
    expr->u.integer = value;

    if (width < AB_CALC_EXPR_MAX_INT_WIDTH) {
        expr->u.integer &= ((1l << width) - 1);
    }
}
