/*
 *  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 <stdlib.h>
#include <math.h>

#include "calc.h"

int edit_len, exp_pos;

void fn_ac(int m, int i)
{
    num_stack[0] = 0.0;
    tok_ptr = 0;
    num_ptr = 0;
    edit = 0;
    last = 0;
    braket = 0;
    value = 0.0;

    redraw();
}

void fn_clr(int m, int i)
{
    if(edit)
    {
	start_edit('0');
	refresh();
    }
}

void fn_del(int m, int i)
{
    if(edit)
    {
	switch(num_str[--edit_len])
	{
	  case '+':
	  case '-':
	    edit &= ~ED_EXP;
	    break;

	  case '.':
	    edit &= ~ED_PNT;
	    break;
	}

	if(edit_len <= 1)
	{
	    num_str[0] = '0';
	    edit_len = 1;
	    edit = 0;
	}

	num_str[edit_len] = 0;
	refresh();
    }
}

void fn_num(int m, int i)
{
    if(i < (int)base)
    {
	if(edit)
	{
	    int max = (edit & ED_EXP) ? exp_pos + 3 :
		(edit & ED_PNT) ? (DIGITS + 1) : DIGITS;

	    if(edit_len > max)
		return;

	    num_str[edit_len] = digit[i];

	    if(edit_len>1 || i)
		num_str[++edit_len] = 0;
	}
	else
	{
	    start_edit(digit[i]);
	}
	refresh();
    }
}

void fn_pnt(int m, int i)
{
    switch(edit)
    {
      case 0:
	start_edit('0');
	/* fallthrough */

      case ED_NUM:
	if(edit_len < 2) edit_len = 2;

	num_str[edit_len] = '.';
	num_str[++edit_len] = 0;
	edit |= ED_PNT;

	refresh();
    }
}

void fn_exp(int m, int i)
{
    if(edit && !(edit & ED_EXP))
    {
	num_str[exp_pos = edit_len] = '+';
	num_str[++edit_len] = 0;
	edit |= ED_EXP;
	refresh();
    }
}

void fn_sgn(int m, int i)
{
    if(edit)
    {
	if(edit & ED_EXP)
	    num_str[exp_pos] = num_str[exp_pos] == '-' ? '+' : '-';
	else
	    num_str[0] = num_str[0] == '-' ? ' ' : '-';
	refresh();
    }
    else
    {
	set_value(-value);
    }
}

void fn_min(int m, int i)
{
    stop_edit();
    mem_val = value;
    redraw();
}

void fn_mr(int m, int i)
{
    stop_edit();
    set_value(mem_val);
}

void fn_mp(int m, int i)
{
    stop_edit();
    mem_val += value;
    redraw();
}

void fn_swp(int m, int i)
{
    double v;
    stop_edit();

    if(m & 1)
    {
	v = mem_val;
	mem_val = value;
    }
    else
    {
	v = num_stack[num_ptr];
	num_stack[num_ptr] = value;
    }
    set_value(v);
}

void fn_rnd(int m, int i)
{
    stop_edit();
    set_value(drand48());
}

void fn_rcp(int m, int i)
{
    stop_edit();
    set_value(1.0 / value);
}

void fn_fct(int m, int i)
{
    double f, c;

    stop_edit();

    for(c = f = 1.0; !isinf(f) && c <= value; c += 1.0)
	f *= c;

    set_value(f);
}

void fn_pow(int m, int i)
{
    stop_edit();
    add_fn(m & 1 ? BIN_ROOT : BIN_POW);
    redraw();
}

void fn_sqr(int m, int i)
{
    stop_edit();
    set_value(m & 1 ?  value * value : sqrt(value));
}

void fn_sin(int m, int i)
{
    double v;
    stop_edit();

    switch(m)
    {
      case 0:
	v = sin(value * drg);
	break;
      case 1:
	v = asin(value) / drg;
	break;
      case 2:
	v = sinh(value);
	break;
      default:
	v = asinh(value);
	break;
    }
    set_value(v);
}

void fn_cos(int m, int i)
{
    double v;
    stop_edit();

    switch(m)
    {
      case 0:
	v = cos(value * drg);
	break;
      case 1:
	v = acos(value) / drg;
	break;
      case 2:
	v = cosh(value);
	break;
      default:
	v = acosh(value);
	break;
    }
    set_value(v);
}

void fn_tan(int m, int i)
{
    double v;
    stop_edit();

    switch(m)
    {
      case 0:
	v = tan(value * drg);
	break;
      case 1:
	v = atan(value) / drg;
	break;
      case 2:
	v = tanh(value);
	break;
      default:
	v = atanh(value);
	break;
    }
    set_value(v);
}

void fn_log(int m, int i)
{
    stop_edit();
    set_value(m & 1 ? pow(10.0, value) : log10(value));
}

void fn_ln(int m, int i)
{
    stop_edit();
    set_value(m & 1 ? exp(value) : log(value));
}

void fn_e(int m, int i)
{
    stop_edit();
    set_value(M_E);
}

void fn_pi(int m, int i)
{
    stop_edit();
    set_value(M_PI);
}

void fn_not(int m, int i)
{
    stop_edit();
    set_value(m_xor(value, -1.0));
}

void fn_bin(int m, int i)
{
    stop_edit();
    add_fn(i);
    redraw();
}

void fn_opn(int m, int i)
{
    stop_edit();
    open_bra();
    redraw();
}

void fn_cls(int m, int i)
{
    stop_edit();
    close_bra();
    redraw();
}

void fn_equ(int m, int i)
{
    stop_edit();
    evaluate();
    redraw();
}
