// $Id: melsecCmd.cpp,v 1.5 2003/03/10 15:45:25 fukasawa Exp $

// ============================================================================
//
// = LIBRARY
//     tclmelsec
//
// = FILENAME
//     melsecCmd.cpp
//
// = AUTHOR(S)
//     Fukasawa Mitsuo
//
// = COPYRIGHT
//     Copyright (c) 2001-2002 by BEE Co.,Ltd.  All rights reserved.
//
//     This file is part of the tclgxml that can be used free.
//     The tclgxml and this file are covered by the GNU Lesser General Public
//     License, which should be included with libxml++ as the file COPYING.
//
// ============================================================================

#ifdef _MSC_VER
#pragma warning(disable: 4786)  // too long identifier
#endif

#include "MELMemory.h"
#include "MELDeviceManager.h"
#ifdef _MSC_VER
#include <windows.h>
#endif
#include "tclbee.h"

// function prototype
extern int parseMemoryAddr(Tcl_Interp * interp, char * devAddr,
                           PLCDevice ** retmem, unsigned long * top);
extern int parseSize(Tcl_Interp * interp, PLCDevice * mem, char * sizeStr,
                     size_t * size);
extern int parseWordData(Tcl_Interp * interp, char * datastr,
                         unsigned short ** bufpp, size_t * size);
extern int parseBitData(Tcl_Interp * interp, char * datastr,
                        unsigned short ** bufpp, size_t * size);
extern int parseData(Tcl_Interp * interp, char * datastr,
                     unsigned short ** bufpp, size_t * size);

//
static bool _initialized = false;

/*
 *----------------------------------------------------------------------
 *
 * Melsec_openCmd --
 *
 *      This procedure is invoked to process the "melsec::open"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int Melsec_openCmd(ClientData arg, Tcl_Interp * interp, int objc,
                   Tcl_Obj *CONST objv[])
{

    static char * opencmds[] = {
        "-path",
        "-channel",
        "-station",
        "-unit",
        "-clear",
        "-xml",
        NULL
    };
    /*
     * All commands enums below ending in X are compatibility
     */
    enum opencmds {
        OPEN_PATH,
        OPEN_CHANNEL,
        OPEN_STATION,
        OPEN_UNIT,
        OPEN_CLEAR,
        OPEN_XML,
    };

    int cmdindex, result = TCL_OK;
    int channelNum = 12;
    int stationNum = 1;
    int unitNum = 0xFF;
    int doClear = 0;
    char * pathName = "\\jyugem\\shared";
    char * xmlName = "\\jyugem\\xml\\melsec.xml";

    Tcl_ResetResult(interp);

    /*
     * Get the command name index from the object based on the spoolcmds
     * defined above.
     */
    int end = objc;
    int i = 1;
    while (i < end)
    {
        if (Tcl_GetIndexFromObj(interp, objv[i], (const char **)opencmds, "option",
                                TCL_EXACT, &cmdindex) != TCL_OK) {
            return TCL_ERROR;
        }
        i++;
        switch ((enum opencmds)cmdindex)
        {
        case OPEN_PATH:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "?-path pathname?");
                result = TCL_ERROR;
                break;
            }
            pathName = Tcl_GetStringFromObj(objv[i++], NULL);
            break;
        case OPEN_XML:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "?-xml xmlname?");
                result = TCL_ERROR;
                break;
            }
            xmlName = Tcl_GetStringFromObj(objv[i++], NULL);
            break;
        case OPEN_CHANNEL:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "?-channel number?");
                result = TCL_ERROR;
                break;
            }
            result = Tcl_GetIntFromObj(interp, objv[i++], &channelNum);
            if (result != TCL_OK) {
                Tcl_SetResult(interp,
                              "Open: Invalid channel number. \n", TCL_STATIC);
                result = TCL_ERROR;
            }
            break;
        case OPEN_STATION:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "?-station number?");
                result = TCL_ERROR;
                break;
            }
            result = Tcl_GetIntFromObj(interp, objv[i++], &stationNum);
            if (result != TCL_OK) {
                Tcl_SetResult(interp,
                              "Open: Invalid station number. \n", TCL_STATIC);
                result = TCL_ERROR;
            }
            break;
        case OPEN_UNIT:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "-unit number");
                result = TCL_ERROR;
                break;
            }
            result = Tcl_GetIntFromObj(interp, objv[i++], &unitNum);
            if (result != TCL_OK) {
                Tcl_SetResult(interp, "Open: Invalid unit number.\n",
                              TCL_STATIC);
            }
            break;

        case OPEN_CLEAR:
            if (i > (end - 1)) {
                Tcl_WrongNumArgs(interp, 2, objv, "-clear flag");
                result = TCL_ERROR;
                break;
            }
            result = Tcl_GetBooleanFromObj(interp, objv[i++], &doClear);
            if (result != TCL_OK) {
                Tcl_SetResult(interp, "Open: Invalid clear flag.\n",
                              TCL_STATIC);
            }
            break;

        }
        if (result != TCL_OK)
            break;
    }
    if (result == TCL_ERROR)
        return (result);

    try {

        PLCMemory::SetClearMemory((doClear == 0) ? false : true);

        MELDeviceManager * melmngr = MELDeviceManager::instance();
        if (melmngr == NULL) {
            return TCL_ERROR;
        }

        result = melmngr->init(pathName, xmlName, channelNum, stationNum,
                               unitNum);
        if (result < 0)
        {
            return TCL_ERROR;
        }
        tm tmtime;
        melmngr->getClock(tmtime);
        melmngr->setClock(tmtime);
        _initialized = true;

    }
    catch (...) {
        Tcl_AppendResult(interp, "open memory error ",
                         NULL);
        return TCL_ERROR;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Melsec_closeCmd --
 *
 *      This procedure is invoked to process the "melsec::close"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_closeCmd(ClientData arg, Tcl_Interp * interp, int objc,
                Tcl_Obj *CONST objv[])
{

    if (objc > 1)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "close ?value value ...?");
        return TCL_ERROR;
    }
    MELDeviceManager * melmngr = MELDeviceManager::instance();
    if (melmngr == NULL)
    {
        return TCL_ERROR;
    }

    melmngr->fini();

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Melsec_readCmd --
 *
 *      This procedure is invoked to process the "melsec::read"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_readCmd(ClientData arg, Tcl_Interp * interp, int objc,
               Tcl_Obj *CONST objv[])
{
    int    result;
    size_t size;
    unsigned long top;
    PLCDevice * mem;

    if (! _initialized)
    {
        Tcl_AppendResult(interp, "not yet opened ", NULL);
        return TCL_ERROR;
    }
    if (objc != 3)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "read addr size ?value value ...?");
        return TCL_ERROR;
    }

    result = parseMemoryAddr(interp, Tcl_GetString(objv[1]), &mem, &top);
    if (result != TCL_OK)
    {
        return result;
    }

    result = parseSize(interp, mem, Tcl_GetString(objv[2]), &size);
    if (result != TCL_OK)
    {
        return result;
    }
    if (top + size >= mem->size())
    {
        Tcl_AppendResult(interp, "size over \"", Tcl_GetString(objv[2]), "\" ",
                         NULL);
        return TCL_ERROR;
    }

    unsigned short * databuf = (unsigned short *)malloc(size * sizeof(short));
    int dumpsize;
    if (mem->isBit())
    {
        dumpsize = (size + BITS_OF_SHORT - 1) / BITS_OF_SHORT;
        int bound_size = dumpsize * BITS_OF_SHORT;
        mem->read(top, bound_size, databuf);
    }
    else
    {
        mem->read(top, size, databuf);
        dumpsize = size;
    }
    bee_memdump(interp, (char *)databuf, dumpsize * 2, DUMP_WORD);

    free(databuf);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Melsec_writeCmd --
 *
 *      This procedure is invoked to process the "melsec::write"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_writeCmd(ClientData arg, Tcl_Interp * interp, int objc,
                Tcl_Obj *CONST objv[])
{
    int    result;
    size_t size;
    unsigned long  top;
    unsigned short * bufptr;
    PLCDevice * mem;

    if (! _initialized)
    {
        Tcl_AppendResult(interp, "not yet opened ", NULL);
        return TCL_ERROR;
    }
    if (objc < 3 || objc > 4)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "write addr data ?value value ...?");
        return TCL_ERROR;
    }
    MELDeviceManager * melmngr = MELDeviceManager::instance();
    if (melmngr == NULL)
    {
        return TCL_ERROR;
    }

    result = parseMemoryAddr(interp, Tcl_GetString(objv[1]), &mem, &top);
    if (result != TCL_OK)
    {
        return result;
    }

    if (EQUALSTR(Tcl_GetString(objv[2]), "-ascii"))
    {
        if (objc != 4)
        {
            Tcl_AppendResult(interp, "No data !?\n", NULL);
            return TCL_ERROR;
        }
        if (mem->isBit())
        {
            Tcl_AppendResult(interp, "Must be word device.\n", NULL);
            return TCL_ERROR;
        }
        char * wrstr = Tcl_GetString(objv[3]);
        int length = strlen(wrstr);
        if (length == 0)
        {
            Tcl_AppendResult(interp, "Write data nothing !?\n", NULL);
            return TCL_ERROR;
        }
        bufptr = (unsigned short *)calloc(length + 8, 1);
        if (bufptr == NULL)
        {
            Tcl_AppendResult(interp, "Lack heap area.\n", NULL);
            return TCL_ERROR;
        }
        strcpy((char *)bufptr, wrstr);
        size = (length + 2) / 2;  // terminate & boundary
    }
    else if (EQUALSTR(Tcl_GetString(objv[2]), "-time"))
    {
        char * timeStr =  Tcl_GetString(objv[3]);
        if (strlen(timeStr) < 14)
        {
            Tcl_AppendResult(interp, "Length of Time must be 14 characters.\n", NULL);
            return TCL_ERROR;
        }
        char yearbuf[8];
        char monthbuf[8];
        char daybuf[8];
        char hourbuf[8];
        char minbuf[8];
        char secbuf[8];
        yearbuf[0] = *(timeStr + 0);
        yearbuf[1] = *(timeStr + 1);
        yearbuf[2] = *(timeStr + 2);
        yearbuf[3] = *(timeStr + 3);
        yearbuf[4] = '\0';
        monthbuf[0] = *(timeStr + 4);
        monthbuf[1] = *(timeStr + 5);
        monthbuf[2] = '\0';
        daybuf[0] = *(timeStr + 6);
        daybuf[1] = *(timeStr + 7);
        daybuf[2] = '\0';
        hourbuf[0] = *(timeStr + 8);
        hourbuf[1] = *(timeStr + 9);
        hourbuf[2] = '\0';
        minbuf[0] = *(timeStr + 10);
        minbuf[1] = *(timeStr + 11);
        minbuf[2] = '\0';
        secbuf[0] = *(timeStr + 12);
        secbuf[1] = *(timeStr + 13);
        secbuf[2] = '\0';
        int year = strtoul(yearbuf, NULL, 10);
        int month = strtoul(monthbuf, NULL, 10);
        int day = strtoul(daybuf, NULL, 10);
        int hour = strtoul(hourbuf, NULL, 10);
        int minute = strtoul(minbuf, NULL, 10);
        int second = strtoul(secbuf, NULL, 10);
        int week = 0;
        size = 4;
        bufptr = (unsigned short *)malloc(sizeof(short) * size);
        *(bufptr + 0) = (unsigned short)((BYTE_TO_BCD(year % 100) << 8) + BYTE_TO_BCD(month));
        *(bufptr + 1) = (unsigned short)((BYTE_TO_BCD(day) << 8) + BYTE_TO_BCD(hour));
        *(bufptr + 2) = (unsigned short)((BYTE_TO_BCD(minute) << 8) + BYTE_TO_BCD(second));
        *(bufptr + 3) = (unsigned short)((BYTE_TO_BCD(year / 100) << 8) + BYTE_TO_BCD(week));
    }
    else
    {
        if (mem->isBit())
        {
            result = parseBitData(interp, Tcl_GetString(objv[2]), &bufptr, &size);
        }
        else
        {
            result = parseWordData(interp, Tcl_GetString(objv[2]), &bufptr, &size);
        }
        if (result != TCL_OK)
        {
            return result;
        }
    }

    result = mem->write(top, size, bufptr);

    free(bufptr);
    return (result == 0) ? TCL_OK : TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * Melsec_xwriteCmd --
 *
 *      This procedure is invoked to process the "melsec::xwrite"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_xwriteCmd(ClientData arg, Tcl_Interp * interp, int objc,
                Tcl_Obj *CONST objv[])
{
    int    result;
    size_t size;
    unsigned long  top;
    unsigned short * bufptr;
    PLCDevice * mem;

    if (! _initialized)
    {
        Tcl_AppendResult(interp, "not yet opened ", NULL);
        return TCL_ERROR;
    }
    if (objc != 3)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "write addr data ?value value ...?");
        return TCL_ERROR;
    }
    MELDeviceManager * melmngr = MELDeviceManager::instance();
    if (melmngr == NULL)
    {
        return TCL_ERROR;
    }

    result = parseMemoryAddr(interp, Tcl_GetString(objv[1]), &mem, &top);
    if (result != TCL_OK)
    {
        return result;
    }

    result = parseData(interp, Tcl_GetString(objv[2]), &bufptr, &size);
    if (result != TCL_OK)
    {
        return result;
    }

    result = mem->write(top, size, bufptr);

    free(bufptr);
    return (result == 0) ? TCL_OK : TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * Melsec_getCmd --
 *
 *      This procedure is invoked to process the "melsec::get"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_getCmd(ClientData arg, Tcl_Interp * interp, int objc,
              Tcl_Obj *CONST objv[])
{
    int result;
    unsigned long top;
    PLCDevice * mem;
    char buf[32];

    if (! _initialized)
    {
        Tcl_AppendResult(interp, "not yet opened ", NULL);
        return TCL_ERROR;
    }
    if (objc != 2)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "get addr");
        return TCL_ERROR;
    }

    result = parseMemoryAddr(interp, Tcl_GetString(objv[1]), &mem, &top);
    if (result != TCL_OK)
    {
        return result;
    }

    unsigned short data = mem->get(top);
    if (mem->isBit())
    {
        strcpy(buf, (data != 0) ? "1" : "0");
    }
    else
    {
        sprintf(buf, "0x%x", data);
    }
    Tcl_AppendResult(interp, buf, NULL);

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Melsec_putCmd --
 *
 *      This procedure is invoked to process the "melsec::put"
 *      command. See the user documentation for details on what it does.
 *
 * Results:
 *      A standard Tcl result.
 *
 * Side effects:
 *      See the user documentation.
 *
 *----------------------------------------------------------------------
 */

    /* ARGSUSED */
int
Melsec_putCmd(ClientData arg, Tcl_Interp * interp, int objc,
                Tcl_Obj *CONST objv[])
{
    int    result;
    unsigned long  top;
    PLCDevice * mem;

    if (! _initialized)
    {
        Tcl_AppendResult(interp, "not yet opened ", NULL);
        return TCL_ERROR;
    }
    if (objc != 3)
    {
        Tcl_WrongNumArgs(interp, 1, objv, "put addr data");
        return TCL_ERROR;
    }
    MELDeviceManager * melmngr = MELDeviceManager::instance();
    if (melmngr == NULL)
    {
        return TCL_ERROR;
    }

    result = parseMemoryAddr(interp, Tcl_GetString(objv[1]), &mem, &top);
    if (result != TCL_OK)
    {
        return result;
    }

    long data;
    if (Tcl_GetLongFromObj(interp, objv[2], &data) != TCL_OK)
    {
        Tcl_AppendResult(interp, "data is not numeric ", NULL);
        return TCL_ERROR;
    }

    result = mem->put(top, (unsigned short)(data & 0xFFFF));

    return (result == 0) ? TCL_OK : TCL_ERROR;
}

