// vim: set ts=4 sw=4 et tw=80:

#include "bexp.h"
#include <stdlib.h>

static int value_length(const value* val);

inline int value_length(const value* val) {
    return val->end - val->begin;
}

typedef int(*compute_func)(char*, int, int, int*);

int bexp_loop(value* result, const expression* ex, compute_func compute) {
    const char* end1 = ex->value1.end;
    const char* end2 = ex->value2.end;

    char* l1 = ex->value1.begin;
    char* l2 = (ex->op == NOT || ex->op == COMPLEMENT) ? NULL :
        ex->value2.begin;

    int acc = 0;

    char* r = result->begin;

    while (l1 < end1 || (l2 != NULL && l2 < end2)) {
        if (r == result->end) {
            return 0;
        }

        const int a = l1 < end1 ? *l1 - '0' : 0;
        const int b = l2 != NULL && l2 < end2 ? *l2 - '0' : 0;

        *r = '0';

        if (compute(r, a, b, &acc)) {
            return 1;
        }

        if (ex->op != GREATER_THAN && ex->op != LESS_THAN && ex->op != NOT)
            r++;
        l1++;
        if (l2 != NULL) l2++;
    }

    if (acc == 1) {
        if (r == result->end) {
            return 0;
        } else {
            *r = '1';
        }
    }

    return 1;
}

int compute_and(char* r, int a, int b, int* _) { *r += a && b; return 0; }
int compute_or(char* r, int a, int b, int* _) { *r += a || b; return 0; }
int compute_xor(char* r, int a, int b, int* _) { *r += a ^ b; return 0; }
int compute_gt(char* r, int a, int b, int* _) { *r += a > b; return a != b; }
int compute_lt(char* r, int a, int b, int* _) { *r += a < b; return a != b; }

int compute_plus(char* r, int a, int b, int* c) {
    *r += a ^ b ^ *c;
    *c = (a && b) || ((a ^ b) && *c);
    return 0;
}

int compute_compl(char* r, int a, int _, int* __) { *r += !a; return 0; }
int compute_not(char* r, int a, int _, int* __) { *r += !a; return a; }

compute_func bexp_compute(const operator_t op) {
    switch (op) {
    case AND:          return compute_and;
    case OR:           return compute_or;
    case XOR:          return compute_xor;
    case PLUS:         return compute_plus;
    case GREATER_THAN: return compute_gt;
    case LESS_THAN:    return compute_lt;
    case NOT:          return compute_not;
    case COMPLEMENT:   return compute_compl;
    }
}

int bexp_length(const expression* ex) {
    switch (ex->op) {
    case AND:
    case OR:
    case XOR: {
        const int l1 = value_length(&ex->value1);
        const int l2 = value_length(&ex->value2);
        return l1 > l2 ? l1 : l2;
    }

    case PLUS: {
        const int l1 = value_length(&ex->value1);
        const int l2 = value_length(&ex->value2);
        const int max = l1 > l2 ? l1 : l2;

        char tmp[max + 1];
        tmp[max] = '0';

        value result = { tmp, tmp + max + 1 };

        if (!bexp_loop(&result, ex, compute_plus)) {
            return -1;
        }

        return tmp[max] == '1' ? max + 1 : max;
    }

    case GREATER_THAN:
    case LESS_THAN:
    case NOT:
        return 1;

    case COMPLEMENT:
        return value_length(&ex->value1);
    }
}

int bexp_evaluate(value* result, expression* ex) {
    return bexp_loop(result, ex, bexp_compute(ex->op));
}

