/*

    TABLES.C

*/

#include <p_std.h>
#include <p_sys.h>
#include <p_file.h>
#include <wlib.h>
#include <hwif.h>


GLREF_D UWORD DatLocked;

LOCAL_D TEXT *cmds[]=
    {
    "rReset score",
    "lLimits",
    "mMode",
    "tTiming",
    "xExit",
    NULL
    };

#define TABLES_ITEMS       5

LOCAL_D H_MENU_DATA mdata[]=
    {
    "Tables",TABLES_ITEMS,
    NULL
    };

GLDEF_D TEXT ** _cmds=(&cmds[0]);
GLDEF_D H_MENU_DATA * _mdata=(&mdata[0]);


LOCAL_D UINT MainWid;

typedef struct
    {
    UWORD TableEnd;
    UWORD MaxTable;
    UWORD Tested;
    UWORD Correct;
    UWORD Timing;
    UWORD Mode;
    } TSTATE;

LOCAL_D TSTATE state={12,12,0,0,5,1};

LOCAL_D TEXT *EnvName="TABLES";
#define ENV_NAME_LEN 6

LOCAL_D ULONG seed;

LOCAL_D VOID *timH;
LOCAL_D WORD timactive;
LOCAL_D WORD timstat;
LOCAL_D ULONG timint;
LOCAL_D WORD timcount;
LOCAL_D WORD timtotcnt;


LOCAL_C VOID DrawLine(INT j,TEXT *pb)
    {
    P_RECT box;

    box.tl.x=4;
    box.br.x=189-4;
    box.tl.y=4+13*j;
    box.br.y=box.tl.y+13;
    gPrintBoxText(&box,11,G_TEXT_ALIGN_CENTRE,0,pb,p_slen(pb));
    }

LOCAL_C VOID DisplayScores(VOID)
    {
    TEXT buf[40];

    p_atos(&buf[0],"Score:  %u correct out of %u",state.Correct,state.Tested);
    DrawLine(0,&buf[0]);
    }

LOCAL_C VOID DisplayMode(VOID)
    {
    TEXT *pb;
    TEXT buf[40];

    if (state.Mode==1)
        pb="Testing all tables";
    else
        {
        p_atos(&buf[0],"Testing %d times table",state.Mode);
        pb=(&buf[0]);
        }
    DrawLine(1,pb);
    }

LOCAL_C VOID DisplayLimits(VOID)
    {
    TEXT buf[40];

    p_atos(&buf[0],"(up to %d times %d)",
                (state.Mode==1? state.MaxTable: state.Mode),state.TableEnd);
    DrawLine(2,&buf[0]);
    }

LOCAL_C VOID DrawButton(INT state)
    {
    P_RECT box;

    box.tl.x=34+38;
    box.br.x=34+38+38;
    box.tl.y=57;
    box.br.y=57+14;
    wDrawButton(&box,"Enter",state);
    }

LOCAL_C VOID DisplayPressButton(VOID)
    {
    gPrintText(34,66,"Press",5);
    DrawButton(FALSE);
    gPrintText(34+38+38+4,66,"to test",7);
    }

LOCAL_C VOID Display(VOID)
    {
    DisplayScores();
    DisplayMode();
    DisplayLimits();
    DisplayPressButton();
    }

LOCAL_C VOID ResetScore(VOID)
    {
    if (!state.Tested)
        {
        wInfoMsg("No score to reset");
        return;
        }
    if (uOpenDialog("Reset score to zero"))
        return;
    if (uAddButtonList("No",-'n',"Yes",'y',NULL))
        return;
    if (uRunDialog()=='y')
        {
        state.Tested=0;
        state.Correct=0;
        DisplayScores();
        }
    }

LOCAL_C VOID ChangeLimits(VOID)
    {
    LONG lTableEnd;
    LONG lMaxTable;
    H_DI_NUMBER end;
    H_DI_NUMBER last;

    lTableEnd=state.TableEnd;
    lMaxTable=state.MaxTable;
    if (uOpenDialog("Limits for testing"))
        return;
    end.value=(&lTableEnd);
    end.low=10;
    end.high=99;
    if (uAddDialogItem(H_DIALOG_NUMBER,"Tables end at",&end))
        return;
    last.value=(&lMaxTable);
    last.low=2;
    last.high=99;
    if (uAddDialogItem(H_DIALOG_NUMBER,"Highest table",&last))
        return;
    if (uRunDialog()<=0)
        return;
    state.TableEnd=lTableEnd;
    state.MaxTable=lMaxTable;
    if (state.Mode>state.MaxTable)
        state.Mode=state.MaxTable;
    DisplayMode();
    DisplayLimits();
    }

LOCAL_C VOID ChangeTiming(VOID)
    {
    LONG lTiming;
    H_DI_NUMBER tim;

    lTiming=state.Timing;
    if (uOpenDialog("Timing"))
        return;
    tim.value=(&lTiming);
    tim.low=1;
    tim.high=20;
    if (uAddDialogItem(H_DIALOG_NUMBER,"Number of seconds allowed",&tim))
        return;
    if (uRunDialog()<=0)
        return;
    state.Timing=lTiming;
    }

GLREF_D UWORD _SmallFontDialog;

LOCAL_C VOID ChangeMode(VOID)
    {
    H_DI_CHOICE ch;
    INT j;
    TEXT buf[20];

    _SmallFontDialog=TRUE;
    if (uOpenDialog("Mode"))
        return;
    if (uBeginDCL(&ch))
        return;
    if (uGrowDCL(&ch,"All tables"))
        return;
    for (j=2; j<=state.MaxTable; j++)
        {
        p_atos(&buf[0],"%d times table",j);
        if (uGrowDCL(&ch,&buf[0]))
            return;
        }
    if (uAddDCL("Test which tables",&state.Mode,&ch))
        return;
    if (uRunDialog()<=0)
        return;
    DisplayMode();
    DisplayLimits();
    }

LOCAL_C VOID ExitApplication(VOID)
    {
    uErrorValue(p_setenviron(EnvName,ENV_NAME_LEN,&state,sizeof(TSTATE)));
    p_exit(0);
    }

LOCAL_C VOID ManageCommand(INT index)
    {
    switch (index)
        {
    case 0:
        ResetScore();
        break;
    case 1:
        ChangeLimits();
        break;
    case 2:
        ChangeMode();
        break;
    case 3:
        ChangeTiming();
        break;
    case 4:
        ExitApplication();
        }
    }

LOCAL_C VOID TryExecuteCommand(INT keycode)
    {
    keycode=uLocateCommand(keycode);
    if (keycode>=0)
        ManageCommand(keycode);
    }

LOCAL_C INT FindRandom(INT low,INT high)
    {
    INT range;

    range=high-low+1;
    return(low+(INT)(p_randl(&seed)%range));
    }

LOCAL_C VOID FindFactors(WORD *pa,WORD *pb)
    {
    *pa=(state.Mode==1? FindRandom(2,state.MaxTable): state.Mode);
    *pb=FindRandom(2,state.TableEnd);
    }

LOCAL_C VOID SwitchStyle(INT style)
    {
    G_GC gc;

    gc.style=style;
    gSetGC(0,G_GC_MASK_STYLE,&gc);
    }

LOCAL_C VOID *CreateEditor(VOID)
    {
    H_EDIT_BOX heb;

    heb.maxchars=4;
    heb.vulen=30;
    heb.pos.x=113;
    heb.pos.y=53;
    heb.win=MainWid;
    heb.font=WS_FONT_BASE+9;
    heb.style=G_STY_DOUBLE;
    return(hEBOpen(H_EDIT_BOX_FONT,&heb));
    }

LOCAL_C VOID ClearBottomArea(VOID)
    {
    P_RECT box;

    box.tl.x=4;
    box.br.x=189-4;
    box.tl.y=4+3*13;
    box.br.y=100-4;
    gClrRect(&box,G_TRMODE_CLR);
    }

LOCAL_C VOID Beep(VOID)
    {
    p_sound(-5,320);
    }

LOCAL_C VOID SelectAll(VOID *ebH)
    {
    hEBSetSelect(ebH,0,p_slen(hEBSenseText(ebH)));
    }

LOCAL_C INT SenseNumberTyped(VOID *ebH)
    {
    TEXT *pb;
    WORD num;

    pb=hEBSenseText(ebH);
    if (!*pb)
        return(0);
    p_stoi(&pb,&num);
    return(num);
    }

LOCAL_C INT AddTextItem(TEXT *pmt,TEXT *pbod)
    {
    H_DI_TEXT txt;
    TEXT buf[20];

    txt.type=H_DTEXT_ALIGN_CENTRE;
    uZTStoBCS(txt.str=(&buf[0]),pbod);
    return(uAddDialogItem(H_DIALOG_TEXT,pmt,&txt));
    }

LOCAL_C INT Complain(INT a,INT b,INT c)
    {
    TEXT buf[20];

    if (uOpenDialog("Incorrect"))
        return(-1);
    p_atos(&buf[0],"%d \236 %d",a,b);
    if (AddTextItem("The question",&buf[0]))
        return(-1);
    buf[p_itob(&buf[0],c)]=0;
    if (AddTextItem("Your answer",&buf[0]))
        return(-1);
    if (uAddButtonList("Help",W_KEY_ESCAPE,"Retry",W_KEY_RETURN,NULL))
        return(-1);
    if (uRunDialog()==W_KEY_RETURN)
        return(0);
    p_atos(&buf[0],"%d \236 %d = %d",a,b,a*b);
    if (uOpenDialog(&buf[0]))
        return(-1);
    if (uAddButtonList("Continue",W_KEY_RETURN,NULL))
        return(-1);
    uRunDialog();
    return(-1);
    }

LOCAL_C INT BadKey(INT k)
    {
    return(k<256 && p_isprint(k) && !p_isdigit(k));
    }

LOCAL_C VOID CancelTimer(VOID)
    {
    if (timactive)
        {
        p_iow2(timH,P_FCANCEL);
        p_waitstat(&timstat);
        }
    timactive=FALSE;
    }

LOCAL_C VOID QueueTimer(VOID)
    {
    CancelTimer();
    p_ioa4(timH,P_FRELATIVE,&timstat,&timint);
    timactive=TRUE;
    }

LOCAL_C VOID InitialiseTimer(VOID)
    {
    if (state.Timing<=4)
        {
        timint=5;
        timcount=2*state.Timing;
        }
    else
        {
        timint=10;
        timcount=state.Timing;
        }
    timtotcnt=timcount;
    QueueTimer();
    }

LOCAL_C VOID GetBarChartRect(P_RECT *pbox)
    {
    pbox->tl.x=9;
    pbox->br.x=180;
    pbox->tl.y=82;
    pbox->br.y=88;
    }

LOCAL_C VOID IncrementBarChart(VOID)
    {
    P_RECT box;

    timcount--;
    GetBarChartRect(&box);
    box.br.x=9+171*(timtotcnt-timcount)/timtotcnt;
    gFillPattern(&box,WS_BITMAP_GREY,G_TRMODE_REPL);
    }

LOCAL_C VOID ClearBarChart(VOID)
    {
    P_RECT box;

    GetBarChartRect(&box);
    gClrRect(&box,G_TRMODE_CLR);
    }

LOCAL_C VOID DisplayQuestion(VOID)
    {
    TEXT buf[30];
    WMSG_KEY key;
    WORD keyactive;
    WORD keystat;
    P_RECT box;
    VOID *ebH;
    WORD a;
    WORD b;
    WORD c;
    WORD ans;

    ebH=CreateEditor();
    if (!ebH)
        return;
    ClearBottomArea();
    hEBEmphasise(ebH,TRUE);
    FindFactors(&a,&b);
    ans=a*b;
    p_atos(&buf[0],"%d \236 %d = ",a,b);
    box.tl.x=53;
    box.br.x=113;
    box.tl.y=52;
    box.br.y=52+22;
    SwitchStyle(G_STY_DOUBLE);
    gPrintBoxText(&box,20,G_TEXT_ALIGN_RIGHT,0,&buf[0],p_slen(&buf[0]));
    SwitchStyle(G_STY_NORMAL);
    box.tl.x=8;
    box.br.x=189-8;
    box.tl.y=81;
    box.br.y=81+8;
    gBorderRect(&box,W_BORD_CORNER_1);
    DatLocked=TRUE;
    state.Tested++;
    keyactive=FALSE;
    InitialiseTimer();
    FOREVER
        {
        if (keyactive)
            wFlush();
        else
            {
            uGetKeyA(&keystat,&key);
            keyactive=TRUE;
            }
        p_iowait();
        if (keystat==E_FILE_PENDING)
            {
            timactive=FALSE;
            IncrementBarChart();
            if (!timcount)
                {
                wFlush();
                p_sleep(2);
                goto CheckAnswer;
                }
            QueueTimer();
            continue;
            }
        keyactive=FALSE;
        if (BadKey(key.keycode))
            Beep();
        else if (key.keycode!=W_KEY_RETURN)
            hEBHandleKey(ebH,key.keycode,key.modifiers);
        else
            {
        CheckAnswer:
            c=SenseNumberTyped(ebH);
            if (c==ans)
                {
                state.Correct++;
                break;
                }
            Beep();
            hEBEmphasise(ebH,FALSE);
            if (Complain(a,b,c))
                break;
            DisplayScores();
            state.Tested++;
            hEBEmphasise(ebH,TRUE);
            SelectAll(ebH);
            ClearBarChart();
            InitialiseTimer();
            }
        }
    DatLocked=FALSE;
    hEBClose(ebH);
    ClearBottomArea();
    DisplayPressButton();
    CancelTimer();
    }

LOCAL_C VOID MakeButtonDance(VOID)
    {
    DrawButton(TRUE);
    wFlush();
    p_sleep(2);
    DrawButton(FALSE);
    wFlush();
    p_sleep(2);
    }

LOCAL_C VOID DoTest(VOID)
    {
    MakeButtonDance();
    DisplayQuestion();
    DisplayScores();
    }

LOCAL_C VOID MainLoop(VOID)
    {
    INT ret;
    WMSG_KEY key;

    FOREVER
        {
        uGetKey(&key);
        if (key.keycode&W_EVENT_KEY)
            {
            if (key.keycode==CONS_EVENT_COMMAND)
                ExitApplication();
            }
        else if (key.keycode&W_SPECIAL_KEY)
            TryExecuteCommand(key.keycode&(~W_SPECIAL_KEY));
        else if (key.keycode==W_KEY_MENU && !(key.modifiers&W_CTRL_MODIFIER))
            {
            ret=uPresentMenus();
            if (ret>0)
                TryExecuteCommand(ret);
            }
        else if (key.keycode==W_KEY_RETURN)
            DoTest();
        }
    }

LOCAL_C VOID ReadState(VOID)
    {
    INT len;
    UBYTE buf[P_ENVMAX];

    len=p_getenviron(EnvName,ENV_NAME_LEN,&buf[0]);
    if (len<0 || len!=sizeof(TSTATE))
        return;
    p_bcpy(&state,&buf[0],sizeof(TSTATE));
    }

LOCAL_C VOID InitSeed(VOID)
    {
    seed=p_date();
    }

LOCAL_C VOID ReduceScreenSize(VOID)
    {
    W_WINDATA wd;

    wd.extent.tl.x=wd.extent.tl.y=0;
    wd.extent.width=189;
    wd.extent.height=100;
    wSetWindow(MainWid,W_WIN_EXTENT,&wd);
    if (uErrorValue(wCheckPoint()))
        p_exit(0);
    }

LOCAL_C VOID CreateGC(VOID)
    {
    INT hgc;

    hgc=gCreateGC0(uFindMainWid());
    if (hgc<0)
        p_exit(hgc);
    }

LOCAL_C VOID SpecificInit(VOID)
    {
    uEscape(FALSE);
    hCrackCommandLine();
    MainWid=uFindMainWid();
    ReduceScreenSize();
    CreateGC();
    gBorder(W_BORD_CORNER_4);
    wsEnable();
    ReadState();
    Display();
    InitSeed();
    p_open(&timH,"TIM:",-1);
    }

GLREF_D UWORD _UseFullScreen;

GLDEF_C VOID main(VOID)
    {
    _UseFullScreen=TRUE;
    uCommonInit();
    SpecificInit();
    MainLoop();
    }
