//-----------------------------------------------------------------------------
// Copyright 2007 Jonathan Westhues
//
// This file is part of LDmicro.
// 
// LDmicro 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 3 of the License, or
// (at your option) any later version.
// 
// LDmicro 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 LDmicro.  If not, see <http://www.gnu.org/licenses/>.
//------
//
// Miscellaneous utility functions that don't fit anywhere else. IHEX writing,
// verified memory allocator, other junk.
//-----------------------------------------------------------------------------
#include "linuxUI.h"
#include <stdio.h>
#include <stdlib.h>

#include "ldmicro.h"

// We should display messages to the user differently if we are running
// interactively vs. in batch (command-line) mode.
BOOL RunningInBatchMode = FALSE;

// Allocate memory on a local heap
HANDLE MainHeap;

// Running checksum as we build up IHEX records.
static int IhexChecksum;

// Try to common a bit of stuff between the dialog boxes, since only one
// can be open at any time.
HWND OkButton;
HWND CancelButton;
BOOL DialogDone;
BOOL DialogCancel;

HFONT MyNiceFont;
HFONT MyFixedFont;

//-----------------------------------------------------------------------------
// printf-like debug function, to the Windows debug log.
//-----------------------------------------------------------------------------
void dbp(char *str, ...)
{
    va_list f;
    char buf[1024];
    va_start(f, str);
    vsprintf(buf, str, f);
    OutputDebugString(buf);
    OutputDebugString("\n");
}

//-----------------------------------------------------------------------------
// Wrapper for AttachConsole that does nothing running under <WinXP, so that
// we still run (except for the console stuff) in earlier versions.
//-----------------------------------------------------------------------------
#define ATTACH_PARENT_PROCESS ((DWORD)-1) // defined in WinCon.h, but only if
                                          // _WIN32_WINNT >= 0x500
// BOOL AttachConsoleDynamic(DWORD base)
// {
//     typedef BOOL WINAPI fptr_acd(DWORD base);
//     fptr_acd *fp;

//     HMODULE hm = LoadLibrary("kernel32.dll");
//     if(!hm) return FALSE;

//     fp = (fptr_acd *)GetProcAddress(hm, "AttachConsole");
//     if(!fp) return FALSE;

//     return fp(base);
// }

//-----------------------------------------------------------------------------
// For error messages to the user; printf-like, to a message box.
//-----------------------------------------------------------------------------
void Error(char *str, ...)
{
    va_list f;
    char buf[1024];
    va_start(f, str);
    vsprintf(buf, str, f);
    va_end(f);
    if(RunningInBatchMode) {
        printf("print batch mode");
        /* Only required for windows
        * AttachConsoleDynamic(ATTACH_PARENT_PROCESS);
        */
        // Indicate that it's an error, plus the output filename
        printf("compile error ('%s'): \n", CurrentCompileFile);
        // The error message itself and an extra newline to be safe.
        printf("%s\n", buf);
    } else {
        //HWND h = GetForegroundWindow();
        //MessageBox(h, buf, _("LDmicro Error"), MB_OK | MB_ICONERROR);
    }
}

//-----------------------------------------------------------------------------
// A standard format for showing a message that indicates that a compile
// was successful.
//-----------------------------------------------------------------------------
void CompileSuccessfulMessage(char *str)
{
    if(RunningInBatchMode) {
        char str[MAX_PATH+100];
        printf("compiled okay, wrote '%s'\n", CurrentCompileFile);

        //AttachConsoleDynamic(ATTACH_PARENT_PROCESS);
        //HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
        //DWORD written;
        //riteFile(h, str, strlen(str), &written, NULL);
    } else {
        MessageBox(MainWindow, str, _("Compile Successful"),
            MB_OK | MB_ICONINFORMATION);
    }
}

//-----------------------------------------------------------------------------
// Check the consistency of the heap on which all the PLC program stuff is
// stored.
//-----------------------------------------------------------------------------
void CheckHeap(char *file, int line)
{
    static unsigned int SkippedCalls;
    static SDWORD LastCallTime;
    SDWORD now = GetTickCount();

    // It slows us down too much to do the check every time we are called;
    // but let's still do the check periodically; let's do it every 70
    // calls or every 20 ms, whichever is sooner.
    if(SkippedCalls < 70 && (now - LastCallTime) < 20) {
        SkippedCalls++;
        return;
    }

    SkippedCalls = 0;
    LastCallTime = now;

    // if(!HeapValidate(MainHeap, 0, NULL)) {
    //     dbp("file %s line %d", file, line);
    //     Error("Noticed memory corruption at file '%s' line %d.", file, line);
    //     oops();
    // }
}

//-----------------------------------------------------------------------------
// Like malloc/free, but memsets memory allocated to all zeros. Also TODO some
// checking and something sensible if it fails.
//-----------------------------------------------------------------------------
void *CheckMalloc(size_t n)
{
    ok();
    void *p = HeapAlloc(MainHeap, HEAP_ZERO_MEMORY, n);
    return p;
}
void CheckFree(void *p)
{
    ok();
    HeapFree(MainHeap, 0, p);
}


//-----------------------------------------------------------------------------
// Clear the checksum and write the : that starts an IHEX record.
//-----------------------------------------------------------------------------
void StartIhex(FILE *f)
{
    fprintf(f, ":");
    IhexChecksum = 0;
}

//-----------------------------------------------------------------------------
// Write an octet in hex format to the given stream, and update the checksum
// for the IHEX file.
//-----------------------------------------------------------------------------
void WriteIhex(FILE *f, BYTE b)
{
    fprintf(f, "%02X", b);
    IhexChecksum += b;
}

//-----------------------------------------------------------------------------
// Write the finished checksum to the IHEX file from the running sum
// calculated by WriteIhex.
//-----------------------------------------------------------------------------
void FinishIhex(FILE *f)
{
    IhexChecksum = ~IhexChecksum + 1;
    IhexChecksum = IhexChecksum & 0xff;
    fprintf(f, "%02X\n", IhexChecksum);
}

//-----------------------------------------------------------------------------
// Create a window with a given client area.
//-----------------------------------------------------------------------------
HWND CreateWindowClient(DWORD exStyle, char *className, char *windowName,
    DWORD style, int x, int y, int width, int height, HWND parent,
    HMENU menu, HINSTANCE instance, void *param)
{
    // HWND h = CreateWindowEx(exStyle, className, windowName, style, x, y,
    //     width, height, parent, menu, instance, param);

    // RECT r;
    // GetClientRect(h, &r);
    // width = width - (r.right - width);
    // height = height - (r.bottom - height);
    
    // SetWindowPos(h, HWND_TOP, x, y, width, height, 0);

    return NULL;
}

//-----------------------------------------------------------------------------
// Window proc for the dialog boxes. This Ok/Cancel stuff is common to a lot
// of places, and there are no other callbacks from the children.
//-----------------------------------------------------------------------------
static LRESULT CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam,
    LPARAM lParam)
{
    // switch (msg) {
    //     case WM_NOTIFY:
    //         break;

    //     case WM_COMMAND: {
    //         HWND h = (HWND)lParam;
    //         if(h == OkButton && wParam == BN_CLICKED) {
    //             DialogDone = TRUE;
    //         } else if(h == CancelButton && wParam == BN_CLICKED) {
    //             DialogDone = TRUE;
    //             DialogCancel = TRUE;
    //         }
    //         break;
    //     }

    //     case WM_CLOSE:
    //     case WM_DESTROY:
    //         DialogDone = TRUE;
    //         DialogCancel = TRUE;
    //         break;

    //     default:
    //         return DefWindowProc(hwnd, msg, wParam, lParam);
    // }

    return 1;
}



//-----------------------------------------------------------------------------
// Set the font of a control to a pretty proportional font (typ. Tahoma).
//-----------------------------------------------------------------------------
void NiceFont(HWND h)
{
//    SendMessage(h, WM_SETFONT, (WPARAM)MyNiceFont, TRUE);
}

//-----------------------------------------------------------------------------
// Set the font of a control to a pretty fixed-width font (typ. Lucida
// Console).
//-----------------------------------------------------------------------------
void FixedFont(HWND h)
{
  //  SendMessage(h, WM_SETFONT, (WPARAM)MyFixedFont, TRUE);
}

//-----------------------------------------------------------------------------
// Create our dialog box class, used for most of the popup dialogs.
//-----------------------------------------------------------------------------
void MakeDialogBoxClass(void)
{
    // WNDCLASSEX wc;
    // memset(&wc, 0, sizeof(wc));
    // wc.cbSize = sizeof(wc);

    // wc.style            = CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_OWNDC |
    //                       CS_DBLCLKS;
    // wc.lpfnWndProc      = (WNDPROC)DialogProc;
    // wc.hInstance        = Instance;
    // wc.hbrBackground    = (HBRUSH)COLOR_BTNSHADOW;
    // wc.lpszClassName    = "LDmicroDialog";
    // wc.lpszMenuName     = NULL;
    // wc.hCursor          = LoadCursor(NULL, IDC_ARROW);
    // wc.hIcon            = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000),
    //                         IMAGE_ICON, 32, 32, 0);
    // wc.hIconSm          = (HICON)LoadImage(Instance, MAKEINTRESOURCE(4000),
    //                         IMAGE_ICON, 16, 16, 0);

    // RegisterClassEx(&wc);

    // MyNiceFont = CreateFont(16, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE,
    //     ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
    //     FF_DONTCARE, "Tahoma");
    // if(!MyNiceFont)
    //     MyNiceFont = (HFONT)GetStockObject(SYSTEM_FONT);

    // MyFixedFont = CreateFont(14, 0, 0, 0, FW_REGULAR, FALSE, FALSE, FALSE,
    //     ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
    //     FF_DONTCARE, "Lucida Console");
    // if(!MyFixedFont)
    //     MyFixedFont = (HFONT)GetStockObject(SYSTEM_FONT);
}

//-----------------------------------------------------------------------------
// Map an I/O type to a string describing it. Used both in the on-screen
// list and when we write a text file to describe it.
//-----------------------------------------------------------------------------
char *IoTypeToString(int ioType)
{
    switch(ioType) {
        case IO_TYPE_DIG_INPUT:         return _("digital in"); 
        case IO_TYPE_DIG_OUTPUT:        return _("digital out"); 
        case IO_TYPE_INTERNAL_RELAY:    return _("int. relay"); 
        case IO_TYPE_UART_TX:           return _("UART tx"); 
        case IO_TYPE_UART_RX:           return _("UART rx"); 
        case IO_TYPE_PWM_OUTPUT:        return _("PWM out"); 
        case IO_TYPE_TON:               return _("turn-on delay"); 
        case IO_TYPE_TOF:               return _("turn-off delay"); 
        case IO_TYPE_RTO:               return _("retentive timer"); 
        case IO_TYPE_COUNTER:           return _("counter"); 
        case IO_TYPE_GENERAL:           return _("general var"); 
        case IO_TYPE_READ_ADC:          return _("adc input"); 
        default:                        return _("<corrupt!>");
    }
}

//-----------------------------------------------------------------------------
// Get a pin number for a given I/O; for digital ins and outs and analog ins,
// this is easy, but for PWM and UART this is forced (by what peripherals
// are available) so we look at the characteristics of the MCU that is in
// use.
//-----------------------------------------------------------------------------
void PinNumberForIo(char *dest, PlcProgramSingleIo *io)
{
    if(!dest) return;

    if(!io) {
        strcpy(dest, "");
        return;
    }
        
    int type = io->type;
    if(type == IO_TYPE_DIG_INPUT || type == IO_TYPE_DIG_OUTPUT
        || type == IO_TYPE_READ_ADC)
    {
        int pin = io->pin;
        if(pin == NO_PIN_ASSIGNED) {
            strcpy(dest, _("(not assigned)"));
        } else {
            sprintf(dest, "%d", pin);
        }
    } else if(type == IO_TYPE_UART_TX && Prog.mcu) {
        if(Prog.mcu->uartNeeds.txPin == 0) {
            strcpy(dest, _("<no UART!>"));
        } else {
            sprintf(dest, "%d", Prog.mcu->uartNeeds.txPin);
        }
    } else if(type == IO_TYPE_UART_RX && Prog.mcu) {
        if(Prog.mcu->uartNeeds.rxPin == 0) {
            strcpy(dest, _("<no UART!>"));
        } else {
            sprintf(dest, "%d", Prog.mcu->uartNeeds.rxPin);
        }
    } else if(type == IO_TYPE_PWM_OUTPUT && Prog.mcu) {
        if(Prog.mcu->pwmNeedsPin == 0) {
            strcpy(dest, _("<no PWM!>"));
        } else {
            sprintf(dest, "%d", Prog.mcu->pwmNeedsPin);
        }
    } else {
        strcpy(dest, "");
    }
}