/*
 *  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, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <stdlib.h>
#include <signal.h>
#include <time.h>
#include <math.h>

#define _CONST_X_STRING
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Toggle.h>

#include "calc.h"

static Widget toplevel;
static Widget dis_opn, dis_num, btn_inv, btn_hyp;

static XtAppContext app_con;

static Atom wm_delete_window;

static String fallback_resources[] =
{
    "xfcalc.translations: #replace <Message>WM_PROTOCOLS: Delete()",

    "*showGrip: off",
    "*defaultDistance: 2",

    "*display*font: -adobe-helvetica-medium-r-normal--18-*",
    "*display*shadowWidth: 0",
    "*display*borderWidth: 0",
    "*display*internalWidth: 0",
    "*display*internalHeight: 0",

    "*buttons*font: -adobe-helvetica-medium-r-normal--10-*",
    "*buttons*width: 24",
    "*buttons*internalWidth: 0",
    "*buttons*internalHeight: 2",

    "*open.label:",
    "*open.justify: left",
    "*open.width: 26",

    "*value.label: 0",
    "*value.justify: right",
    "*value.width: 222",
    "*value.fromHoriz: open",

    "*Inv.label: Inv",
    "*Hyp.label: Hyp",
    "*DRG.label: Deg",
    "*Base.label: Dec",
    "*n_D.label: D",
    "*n_E.label: E",
    "*n_F.label: F",
    "*Clr.label: Clr",
    "*AC.label: AC",

    "*Min.label: Min",
    "*Mr.label: Mr",
    "*M+.label: M+",
    "*Swp.label: Swp",
    "*n_A.label: A",
    "*n_B.label: B",
    "*n_C.label: C",
    "*Rnd.label: Rnd",
    "*Del.label: Del",

    "*Rcp.label: Rcp",
    "*Fct.label: Fct",
    "*Pow.label: Pow",
    "*Sqr.label: Sqr",
    "*n_7.label: 7",
    "*n_8.label: 8",
    "*n_9.label: 9",
    "*Opn.label: (",
    "*Cls.label: )",

    "*Sin.label: Sin",
    "*Cos.label: Cos",
    "*Tan.label: Tan",
    "*Mod.label: Mod",
    "*n_4.label: 4",
    "*n_5.label: 5",
    "*n_6.label: 6",
    "*Mul.label: \327",
    "*Div.label: \367",

    "*Log.label: Log",
    "*Ln.label: Ln",
    "*E.label: e",
    "*Pi.label: Pi",
    "*n_1.label: 1",
    "*n_2.label: 2",
    "*n_3.label: 3",
    "*Add.label: +",
    "*Sub.label: -",

    "*And.label: And",
    "*Or.label: Or",
    "*Xor.label: Xor",
    "*Not.label: Not",
    "*n_0.label: 0",
    "*Pnt.label: .",
    "*Exp.label: Exp",
    "*Equ.label: =",
    "*Sgn.label: +/-",

    "*Hyp.fromHoriz: Inv",
    "*DRG.fromHoriz: Hyp",
    "*Base.fromHoriz: DRG",
    "*n_D.fromHoriz: Base",
    "*n_E.fromHoriz: n_D",
    "*n_F.fromHoriz: n_E",
    "*Clr.fromHoriz: n_F",
    "*AC.fromHoriz: Clr",

    "*Min.fromVert: Inv",
    "*Mr.fromHoriz: Min",
    "*Mr.fromVert: Hyp",
    "*M+.fromHoriz: Mr",
    "*M+.fromVert: DRG",
    "*Swp.fromHoriz: M+",
    "*Swp.fromVert: Base",
    "*n_A.fromHoriz: Swp",
    "*n_A.fromVert: n_D",
    "*n_B.fromHoriz: n_A",
    "*n_B.fromVert: n_E",
    "*n_C.fromHoriz: n_B",
    "*n_C.fromVert: n_F",
    "*Rnd.fromHoriz: n_C",
    "*Rnd.fromVert: Clr",
    "*Del.fromHoriz: Rnd",
    "*Del.fromVert: AC",

    "*Rcp.fromVert: Min",
    "*Fct.fromHoriz: Rcp",
    "*Fct.fromVert: Mr",
    "*Pow.fromHoriz: Fct",
    "*Pow.fromVert: M+",
    "*Sqr.fromHoriz: Pow",
    "*Sqr.fromVert: Swp",
    "*n_7.fromHoriz: Sqr",
    "*n_7.fromVert: n_A",
    "*n_8.fromHoriz: n_7",
    "*n_8.fromVert: n_B",
    "*n_9.fromHoriz: n_8",
    "*n_9.fromVert: n_C",
    "*Opn.fromHoriz: n_9",
    "*Opn.fromVert: Rnd",
    "*Cls.fromHoriz: Opn",
    "*Cls.fromVert: Del",

    "*Sin.fromVert: Rcp",
    "*Cos.fromHoriz: Sin",
    "*Cos.fromVert: Fct",
    "*Tan.fromHoriz: Cos",
    "*Tan.fromVert: Pow",
    "*Mod.fromHoriz: Tan",
    "*Mod.fromVert: Sqr",
    "*n_4.fromHoriz: Mod",
    "*n_4.fromVert: n_7",
    "*n_5.fromHoriz: n_4",
    "*n_5.fromVert: n_8",
    "*n_6.fromHoriz: n_5",
    "*n_6.fromVert: n_9",
    "*Mul.fromHoriz: n_6",
    "*Mul.fromVert: Opn",
    "*Div.fromHoriz: Mul",
    "*Div.fromVert: Cls",

    "*Log.fromVert: Sin",
    "*Ln.fromHoriz: Log",
    "*Ln.fromVert: Cos",
    "*E.fromHoriz: Ln",
    "*E.fromVert: Tan",
    "*Pi.fromHoriz: E",
    "*Pi.fromVert: Mod",
    "*n_1.fromHoriz: Pi",
    "*n_1.fromVert: n_4",
    "*n_2.fromHoriz: n_1",
    "*n_2.fromVert: n_5",
    "*n_3.fromHoriz: n_2",
    "*n_3.fromVert: n_6",
    "*Add.fromHoriz: n_3",
    "*Add.fromVert: Mul",
    "*Sub.fromHoriz: Add",
    "*Sub.fromVert: Div",

    "*And.fromVert: Log",
    "*Or.fromHoriz: And",
    "*Or.fromVert: Ln",
    "*Xor.fromHoriz: Or",
    "*Xor.fromVert: E",
    "*Not.fromHoriz: Xor",
    "*Not.fromVert: Pi",
    "*n_0.fromHoriz: Not",
    "*n_0.fromVert: n_1",
    "*Pnt.fromHoriz: n_0",
    "*Pnt.fromVert: n_2",
    "*Exp.fromHoriz: Pnt",
    "*Exp.fromVert: n_3",
    "*Equ.fromHoriz: Exp",
    "*Equ.fromVert: Add",
    "*Sgn.fromHoriz: Equ",
    "*Sgn.fromVert: Sub",

    "*translations: #override "
    ":<Key>0:Fn(n_0)\\n"
    ":<Key>1:Fn(n_1)\\n"
    ":<Key>2:Fn(n_2)\\n"
    ":<Key>3:Fn(n_3)\\n"
    ":<Key>4:Fn(n_4)\\n"
    ":<Key>5:Fn(n_5)\\n"
    ":<Key>6:Fn(n_6)\\n"
    ":<Key>7:Fn(n_7)\\n"
    ":<Key>8:Fn(n_8)\\n"
    ":<Key>9:Fn(n_9)\\n"
    "<Key>a:Fn(n_A)\\n"
    "<Key>b:Fn(n_B)\\n"
    "<Key>c:Fn(n_C)\\n"
    "<Key>d:Fn(n_D)\\n"
    "<Key>e:Fn(n_E)\\n"
    "<Key>f:Fn(n_F)\\n"
    ":<Key>.:Fn(Pnt)\\n"
    ":<Key>+:Fn(Add)\\n"
    ":<Key>-:Fn(Sub)\\n"
    ":<Key>*:Fn(Mul)\\n"
    ":<Key>/:Fn(Div)\\n"
    ":<Key>(:Fn(Opn)\\n"
    ":<Key>):Fn(Cls)\\n"
    "<Key>[:Fn(Opn)\\n"
    "<Key>]:Fn(Cls)\\n"
    ":<Key>=:Fn(Equ)\\n"
    ":<Key>!:Fn(Fct)\\n"
    ":<Key>^:Fn(Pow)\\n"
    ":<Key>backslash:Fn(Sgn)\\n"
    "<Key>x:Fn(Exp)\\n"
    "<Key>space:Fn(Clr)\\n"
    "<Key>BackSpace:Fn(Del)\\n"
    "<Key>Delete:Fn(Clr)\\n"
    "<Key>Escape:Fn(AC)\\n"
    "<Key>Return:Fn(Equ)\\n"
    "<Key>KP_0:Fn(n_0)\\n"
    "<Key>KP_1:Fn(n_1)\\n"
    "<Key>KP_2:Fn(n_2)\\n"
    "<Key>KP_3:Fn(n_3)\\n"
    "<Key>KP_4:Fn(n_4)\\n"
    "<Key>KP_5:Fn(n_5)\\n"
    "<Key>KP_6:Fn(n_6)\\n"
    "<Key>KP_7:Fn(n_7)\\n"
    "<Key>KP_8:Fn(n_8)\\n"
    "<Key>KP_9:Fn(n_9)\\n"
    "<Key>KP_Decimal:Fn(Pnt)\\n"
    "<Key>KP_Add:Fn(Add)\\n"
    "<Key>KP_Subtract:Fn(Sub)\\n"
    "<Key>KP_Multiply:Fn(Mul)\\n"
    "<Key>KP_Divide:Fn(Div)\\n"
    "<Key>KP_Equal:Fn(Equ)\\n"
    "<Key>KP_Enter:Fn(Equ)",

    NULL
};

static void do_drg(Widget, XtPointer, XtPointer);
static void do_base(Widget, XtPointer, XtPointer);
static void do_function(Widget, XtPointer, XtPointer);

static const struct { void (*proc)(int, int); int data; char name[4]; }
    data[] =
{
    { fn_num, 0xD,	"n_D" },
    { fn_num, 0xE,	"n_E" },
    { fn_num, 0xF,	"n_F" },
    { fn_clr, 0,	"Clr" },
    { fn_ac,  0,	"AC" },
    { fn_min, 0,	"Min" },
    { fn_mr,  0,	"Mr" },
    { fn_mp,  0,	"M+" },
    { fn_swp, 0,	"Swp" },
    { fn_num, 0xA,	"n_A" },
    { fn_num, 0xB,	"n_B" },
    { fn_num, 0xC,	"n_C" },
    { fn_rnd, 0,	"Rnd" },
    { fn_del, 0,	"Del" },
    { fn_rcp, 0,	"Rcp" },
    { fn_fct, 0,	"Fct" },
    { fn_pow, 0,	"Pow" },
    { fn_sqr, 0,	"Sqr" },
    { fn_num, 7,	"n_7" },
    { fn_num, 8,	"n_8" },
    { fn_num, 9,	"n_9" },
    { fn_opn, 0,	"Opn" },
    { fn_cls, 0,	"Cls" },
    { fn_sin, 0,	"Sin" },
    { fn_cos, 0,	"Cos" },
    { fn_tan, 0,	"Tan" },
    { fn_bin, BIN_MOD,	"Mod" },
    { fn_num, 4,	"n_4" },
    { fn_num, 5,	"n_5" },
    { fn_num, 6,	"n_6" },
    { fn_bin, BIN_MUL,	"Mul" },
    { fn_bin, BIN_DIV,	"Div" },
    { fn_log, 0,	"Log" },
    { fn_ln,  0,	"Ln" },
    { fn_e,   0,	"E" },
    { fn_pi,  0,	"Pi" },
    { fn_num, 1,	"n_1" },
    { fn_num, 2,	"n_2" },
    { fn_num, 3,	"n_3" },
    { fn_bin, BIN_ADD,	"Add" },
    { fn_bin, BIN_SUB,	"Sub" },
    { fn_bin, BIN_AND,	"And" },
    { fn_bin, BIN_OR,	"Or" },
    { fn_bin, BIN_XOR,	"Xor" },
    { fn_not, 0,	"Not" },
    { fn_num, 0,	"n_0" },
    { fn_pnt, 0,	"Pnt" },
    { fn_exp, 0,	"Exp" },
    { fn_equ, 0,	"Equ" },
    { fn_sgn, 0,	"Sgn" }
};

static void function(Widget, XEvent *, String *, Cardinal *);
static void delete(Widget, XEvent *, String *, Cardinal *);

static XtActionsRec actions[] =
{
    { "Fn", function },
    { "Delete", delete }
};

static const char inc[3] = { 1, 2, 0 };

int main(int argc, char **argv)
{
    Widget paned, form, w;
    size_t i;

    toplevel = XtAppInitialize(&app_con, "Xfcalc", 0, 0, &argc, argv,
			       fallback_resources, 0, 0);
    XtAppAddActions(app_con, actions, XtNumber(actions));

    paned = XtCreateManagedWidget("paned", panedWidgetClass, toplevel, 0, 0);

    form = XtCreateManagedWidget("display", formWidgetClass, paned, 0, 0);

    dis_opn = XtCreateManagedWidget("open", labelWidgetClass, form, 0, 0);

    dis_num = XtCreateManagedWidget("value", labelWidgetClass, form, 0, 0);

    form = XtCreateManagedWidget("buttons", formWidgetClass, paned, 0, 0);

    btn_inv = XtCreateManagedWidget("Inv", toggleWidgetClass, form, 0, 0);
    btn_hyp = XtCreateManagedWidget("Hyp", toggleWidgetClass, form, 0, 0);

    w = XtCreateManagedWidget("DRG", commandWidgetClass, form, 0, 0);
    XtAddCallback(w, XtNcallback, do_drg, NULL);

    w = XtCreateManagedWidget("Base", commandWidgetClass, form, 0, 0);
    XtAddCallback(w, XtNcallback, do_base, NULL);

    for(i = 0; i < (sizeof(data) / sizeof(data[0])); i++)
    {
	w = XtCreateManagedWidget(data[i].name, commandWidgetClass,
				  form, 0, 0);
	XtAddCallback(w, XtNcallback, do_function, (XtPointer)i);
    }

    XtSetMappedWhenManaged(toplevel, 0);
    XtRealizeWidget(toplevel);

    wm_delete_window = XInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW", 0);
    XSetWMProtocols(XtDisplay(toplevel), XtWindow(toplevel),
		    &wm_delete_window, 1);
    XStoreName(XtDisplay(toplevel), XtWindow(toplevel), "Calculator");
    XSetIconName(XtDisplay(toplevel), XtWindow(toplevel), "Calc");

    XtMapWidget(toplevel);

    srand48(time(NULL));
    signal(SIGFPE, SIG_IGN);

    XtAppMainLoop(app_con);

    XtDestroyApplicationContext(app_con);

    return 1;
}

void refresh(void)
{
    Arg arg;

    XtSetArg(arg, XtNlabel, bra_str);
    XtSetValues(dis_opn, &arg, 1);
    arg.value = (XtArgVal)num_str;
    XtSetValues(dis_num, &arg, 1);
}

static int get_modifiers(void)
{
    Arg arg;
    int i = 0, h = 0, m = 0;

    XtSetArg(arg, XtNstate, &i);
    XtGetValues(btn_inv, &arg, 1);
    arg.value = (XtArgVal)0;
    XtSetValues(btn_inv, &arg, 1);

    arg.value = (XtArgVal)&h;
    XtGetValues(btn_hyp, &arg, 1);
    arg.value = (XtArgVal)0;
    XtSetValues(btn_hyp, &arg, 1);

    if(i) m |= 1;
    if(h) m |= 2;

    return m;
}

static void do_drg(Widget w, XtPointer user_data, XtPointer call_data)
{
    static const double tab[3] = { M_PI/180.0, 1.0, M_PI/200.0 };
    static const char label[3][4] = { "Deg", "Rad", "Gra" };
    static int i = 0;
    Arg arg;

    i = inc[i];

    XtSetArg(arg, XtNlabel, label[i]);
    XtSetValues(w, &arg, 1);

    get_modifiers();
    drg = tab[i];
}

static void do_base(Widget w, XtPointer user_data, XtPointer call_data)
{
    static const double tab[3] = { 10.0, 16.0, 8.0 };
    static const char label[3][4] = { "Dec", "Hex", "Oct" };
    static int i = 0;
    Arg arg;

    i = inc[i];

    XtSetArg(arg, XtNlabel, label[i]);
    XtSetValues(w, &arg, 1);

    get_modifiers();
    stop_edit();

    base = tab[i];
    redraw();
}

static void do_function(Widget w, XtPointer user_data, XtPointer call_data)
{
    size_t i = (size_t)user_data;

    data[i].proc(get_modifiers(), data[i].data);
}

static void function(Widget w, XEvent *event, String *args, Cardinal *num)
{
    size_t i;

    for(i = 0; i < (sizeof(data) / sizeof(data[0])); i++)
    {
	if(!strcmp(args[0],data[i].name))
	{
	    data[i].proc(get_modifiers(), data[i].data);
	    return;
	}
    }
}

static void delete(Widget w, XEvent *event, String *args, Cardinal *num)
{
    if(event->xclient.data.l[0] == wm_delete_window)
    {
	XtAppSetExitFlag(app_con);
    }
}
