/*
 *  Xfcalc version 1.1 - A multi-base calculator for X
 *  Copyright (C) 2026 Christopher John Purnell
 *                     cjp@lost.org.uk
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not see <https://www.gnu.org/licenses/>.
 */

#include <stdint.h>
#include <math.h>

#include "calc.h"

static int eval_to(int);

static double m_add(double, double);
static double m_sub(double, double);
static double m_mul(double, double);
static double m_div(double, double);
static double m_root(double, double);

void open_bra(void)
{
    if(braket < 9)
    {
	braket++;

	if(last > 0)
	{
	    tok_stack[tok_ptr++] = BIN_MUL;
	    num_stack[++num_ptr] = value;
	}
	tok_stack[tok_ptr++] = BRAKET;

	value = 0.0;
	last = 0;
    }
}

void add_fn(int token)
{
    if(last < 0)
    {
	num_ptr--;
	tok_ptr--;
    }

    eval_to(token & LVL_MASK);

    tok_stack[tok_ptr++] = token;
    num_stack[++num_ptr] = value;

    last = -1;
}

void close_bra(void)
{
    if(eval_to(BRAKET))
    {
	tok_ptr--;
	braket--;
    }

    last = 1;
}

void evaluate(void)
{
    while(eval_to(BRAKET))
    {
	tok_ptr--;
	braket--;
    }

    num_stack[0] = 0.0;
    last = 0;
}

typedef double (*m_fn)(double, double);

static int eval_to(int token)
{
    int t;
    static m_fn tab[] =
    {
	m_sub, m_add, m_or,
	m_mul, m_div, fmod, m_and,
	pow, m_root, m_xor
    };

    if(tok_ptr)
    {
	do
	{
	    if(!(t = tok_stack[tok_ptr-1]))
		break;
	    if(t < token)
		break;

	    value = tab[t & FN_MASK](num_stack[num_ptr], value);

	    --num_ptr;
	}
	while(--tok_ptr);
    }

    return tok_ptr;
}

static double m_sub(double x, double y)
{
    return x - y;
}

static double m_add(double x, double y)
{
    return x + y;
}

static double m_mul(double x, double y)
{
    return x * y;
}

static double m_div(double x, double y)
{
    return x / y;
}

static double m_root(double x, double y)
{
    return pow(x, 1.0 / y);
}

double m_and(double x, double y)
{
    int ex, ey;
    double nx = frexp(x, &ex);
    double ny = frexp(y, &ey);
    int em = -1023;
    int64_t vx, vy;

    if(islessgreater(nx, 0.0) && ex > em) em = ex;
    if(islessgreater(ny, 0.0) && ey > em) em = ey;

    em = 63 - em;

    vx = (int64_t)floor(ldexp(x, em));
    vy = (int64_t)floor(ldexp(y, em));

    return ldexp((double)(vx & vy), -em);
}

double m_or(double x, double y)
{
    int ex, ey;
    double nx = frexp(x, &ex);
    double ny = frexp(y, &ey);
    int em = -1023;
    int64_t vx, vy;

    if(islessgreater(nx, 0.0) && ex > em) em = ex;
    if(islessgreater(ny, 0.0) && ey > em) em = ey;

    em = 63 - em;

    vx = (int64_t)floor(ldexp(x, em));
    vy = (int64_t)floor(ldexp(y, em));
	
    return ldexp((double)(vx | vy), -em);
}

double m_xor(double x, double y)
{
    int ex, ey;
    double nx = frexp(x, &ex);
    double ny = frexp(y, &ey);
    int em = -1023;
    int64_t vx, vy;

    if(islessgreater(nx, 0.0) && ex > em) em = ex;
    if(islessgreater(ny, 0.0) && ey > em) em = ey;

    em = 63 - em;

    vx = (int64_t)floor(ldexp(x, em));
    vy = (int64_t)floor(ldexp(y, em));

    return ldexp((double)(vx ^ vy), -em);
}
