// $Id: MELDeviceManager.cpp,v 1.5 2003/03/19 16:49:24 fukasawa Exp $

//=============================================================================
/**
 *  @file    MELDeviceManager.cpp
 *
 *  @author  Fukasawa Mitsuo
 *
 *
 *    Copyright (C) 2001-2002 BEE Co.,Ltd. All rights reserved.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
//=============================================================================

#define BEE_BUILD_DLL

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

#include "MELDeviceManager.h"
#include "MELMemory.h"
#include "ace/Log_Msg.h"
#if !defined(PLC_SHARED_MEMORY)
#ifndef mdopen
#include "Mdfunc.h"
#endif
#endif
#include "MELXmlParser.h"

//------------------------------------------------------------------------------
//
// Implementate
//
//------------------------------------------------------------------------------
struct MelsecRegInfo
{
    int    m_memtype;
    ACE_TCHAR * m_name;
    int    m_devcode;
    size_t m_size;
};

static MelsecRegInfo _registers[] =
{
    { MELMEM_BIT,  ACE_TEXT("X"),   MELDEV_X,   8192  },   // DevX
    { MELMEM_BIT,  ACE_TEXT("Y"),   MELDEV_Y,   8192  },   // DevY
    { MELMEM_BIT,  ACE_TEXT("B"),   MELDEV_B,   12288 },   // DevB
    { MELMEM_BIT,  ACE_TEXT("CT"),  MELDEV_CT,  1024  },   // DevCT
    { MELMEM_BIT,  ACE_TEXT("CC"),  MELDEV_CC,  1024  },   // DevCC
    { MELMEM_WORD, ACE_TEXT("CN"),  MELDEV_CN,  1024  },   // DevCN
    { MELMEM_BIT,  ACE_TEXT("CM"),  MELDEV_CM,  1024  },   // DevCM
    { MELMEM_BIT,  ACE_TEXT("CS"),  MELDEV_CS,  1024  },   // DevCS
    { MELMEM_BIT,  ACE_TEXT("C2"),  MELDEV_C2,  1024  },   // DevC2
    { MELMEM_BIT,  ACE_TEXT("C3"),  MELDEV_C3,  1024  },   // DevC3
    { MELMEM_WORD, ACE_TEXT("D"),   MELDEV_D,   18432 },   // DevD (0x4800)
    { MELMEM_WORD, ACE_TEXT("SD"),  MELDEV_SD,  8192  },   // DevSD
    { MELMEM_BIT,  ACE_TEXT("L"),   MELDEV_L,   3072  },   // DevL
    { MELMEM_BIT,  ACE_TEXT("M"),   MELDEV_M,   32768 },   // DevM
    { MELMEM_WORD, ACE_TEXT("R"),   MELDEV_R,   32768 },   // DevR
//  { MELMEM_BIT,  ACE_TEXT("S"),   MELDEV_S,   8192  },   // DevS
    { MELMEM_BIT,  ACE_TEXT("SB"),  MELDEV_SB,  2048  },   // DevSB
    { MELMEM_BIT,  ACE_TEXT("SM"),  MELDEV_SM,  8192  },   // DevSM
    { MELMEM_BIT,  ACE_TEXT("SW"),  MELDEV_SW,  2048  },   // DevSW
    { MELMEM_BIT,  ACE_TEXT("TT"),  MELDEV_TT,  3072  },   // DevTT
    { MELMEM_BIT,  ACE_TEXT("TC"),  MELDEV_TC,  3072  },   // DevTC
    { MELMEM_BIT,  ACE_TEXT("TM"),  MELDEV_TM,  3072  },   // DevTM
    { MELMEM_BIT,  ACE_TEXT("TS"),  MELDEV_TS,  3072  },   // DevTS
    { MELMEM_BIT,  ACE_TEXT("TS2"), MELDEV_TS2, 3072  },   // DevTS2
    { MELMEM_BIT,  ACE_TEXT("TS3"), MELDEV_TS3, 3072  },   // DevTS3
    { MELMEM_BIT,  ACE_TEXT("V"),   MELDEV_V,   2048  },   // DevV
//  { MELMEM_WORD, ACE_TEXT("W"),   MELDEV_W,   2048  },   // DevW
    { MELMEM_ZR,   ACE_TEXT("ZR"),  MELDEV_ZR,  0xFE800 }, // DevZR
    { MELMEM_UNKNOWN, NULL,  0, 0 }              // terminate
};

//
static MELDeviceManager * _deviceManager = NULL;


//-----------------------------------------------------------------------------
MELDeviceManager::~MELDeviceManager()
{
#if !defined(PLC_SHARED_MEMORY)
    mdClose(m_path);                  // closed MELSEC device
#endif
    for (u_int i = 0; i < m_iodev.size(); i++)
    {
        PLCDevice * memptr = m_iodev[i];
        if (memptr != NULL)
            delete memptr;
    }
}

//-----------------------------------------------------------------------------
// Initialize Device Manager Object.
//-----------------------------------------------------------------------------
int MELDeviceManager::init(const ACE_TCHAR * pname, const ACE_TCHAR * xmlname,
                           int chan, int stnum, int unit)
{
    int result;
    result = this->PLCDeviceManager::init(pname, xmlname, chan, stnum, unit);
    if (result < 0)
    {
        return result;
    }

#if defined(PLC_SHARED_MEMORY)
    // set path name for mmap
    PLCMemory::pathName(m_basedir);
    m_path = 0;
#else
    // Open MELSEC
    // int result = QBF_Open(m_unit, &m_path);
    result = mdOpen(m_channel, -1, &m_path);
    if (result != MEL_NORMAL)
    {
        const ACE_TCHAR * errtext = MELDeviceManager::instance()->errmsg(result);
        ACE_DEBUG((LM_DEBUG, ACE_TEXT("MELSEC ERROR (init): %s.\n"), errtext));
        return -1;
    }

#endif

    MELXmlParser * xmlParser = MELXmlParser::instance();
    if (xmlname != NULL)
    {
        xmlParser->init(xmlname);
        xmlParser->parse(this);
        xmlParser->fini();
    }
    else
    {
        result = initDefaultRegisters();
    }

    return result;
}

//------------------------------------------------------------------------------
// Enter Device Info to Manager Object.
//------------------------------------------------------------------------------
int MELDeviceManager::initRegisters(MELXmlParser * xmlParser)
{
    int result = 0;
    PLCMemory * plc;
    for (u_int i = 0; i < xmlParser->m_melsecs.size(); i++)
    {
        MelsecMemoryInfo * regs = &(xmlParser->m_melsecs[i]);
        if (regs->m_memtype == MELMEM_BIT)
        {
            MELBitMemory * bit_reg =
                new MELBitMemory(regs->m_name, regs->m_devcode, regs->m_maxsize);
            plc = bit_reg;
        }
        else if (regs->m_memtype == MELMEM_WORD)
        {
            MELWordMemory * word_reg =
                new MELWordMemory(regs->m_name, regs->m_devcode, regs->m_maxsize);
            plc = word_reg;
        }
        else /* if (regs->m_memtype == MELMEM_ZR) */
        {
            MEL_ZR_Memory * zr_reg = new MEL_ZR_Memory();
            plc = zr_reg;
        }
        result = plc->open(m_path, m_stationNum);
        if (result < 0)
        {
            break;
        }
        this->add(plc);
    }

    return result;
}

//------------------------------------------------------------------------------
// Enter Device Info to Manager Object.
//------------------------------------------------------------------------------
int MELDeviceManager::initDefaultRegisters()
{
    int result = 0;
    MelsecRegInfo * regs = _registers;
    while (regs->m_memtype != MELMEM_UNKNOWN)
    {
        PLCMemory * plc;
        if (regs->m_memtype == MELMEM_BIT)
        {
            MELBitMemory * bit_reg =
                new MELBitMemory(regs->m_name, regs->m_devcode, regs->m_size);
            plc = bit_reg;
        }
        else if (regs->m_memtype == MELMEM_WORD)
        {
            MELWordMemory * word_reg =
                new MELWordMemory(regs->m_name, regs->m_devcode, regs->m_size);
            plc = word_reg;
        }
        else /* if (regs->m_memtype == MELMEM_ZR) */
        {
            MEL_ZR_Memory * zr_reg = new MEL_ZR_Memory();
            plc = zr_reg;
        }
        result = plc->open(m_path, m_stationNum);
        if (result < 0)
        {
            break;
        }
        this->add(plc);
        regs++;
    }

    return result;
}

//-----------------------------------------------------------------------------
// Finish Device Manager Object.
//-----------------------------------------------------------------------------
int MELDeviceManager::fini()
{
#if !defined(PLC_SHARED_MEMORY)
    // Close MELSEC
    // int result = QBF_Close(m_path);
    int result = mdClose(m_path);
#endif
    return 0;
}

//------------------------------------------------------------------------------
// Initialize virtual melsec memory.
//------------------------------------------------------------------------------
int MELDeviceManager::setVirtualAddress()
{
    int result = 0;

    char * vmtop = (char *)m_vmtop;
    PLCDevice * melsec;

    for (u_int i = 0; i < m_vmtop->m_devq; i++)
    {
        melsec = this->get(m_vmtop->m_vdevs[i].m_devcode);
        if (melsec == NULL)
        {
            ACE_ERROR((LM_ERROR,
                "MELDeviceManager::setVirtualAddress: device(%d) not found.\n",
                m_vmtop->m_vdevs[i].m_devcode));
            continue;
        }
        melsec->vm((u_short *)(vmtop + m_vmtop->m_vdevs[i].m_offset));
    }

    return result;
}


//------------------------------------------------------------------------------
// Instance Device Manager Object.
//------------------------------------------------------------------------------
MELDeviceManager * MELDeviceManager::instance()
{
    if (_deviceManager == NULL)
    {   // Create Device Manager
        _deviceManager = new MELDeviceManager();
        PLCDeviceManager::instance(_deviceManager); //set parent's global access
    }
    return _deviceManager;
}

//------------------------------------------------------------------------------
MELDeviceManager * MELDeviceManager::instance(MELDeviceManager * child)
{
    if (_deviceManager != NULL)
    {
        ACE_ERROR((LM_ERROR,
                   "MELDeviceManager::instance: manager is exist.\n"));
        delete _deviceManager;
    }
    // Initialize Device Manager
    _deviceManager = child;
    PLCDeviceManager::instance(_deviceManager); //set parent's global access
    return _deviceManager;
}

//------------------------------------------------------------------------------
// Parse device address to convert string to device code and offset.
//------------------------------------------------------------------------------
int MELDeviceManager::parseAddress(const string& addrStr,
                                   PLCAccess& paccess) const
{
    ACE_TCHAR devName[8];
    const ACE_TCHAR * paddr = addrStr.c_str();

    UINT pos = ACE_OS::strcspn(paddr, ACE_TEXT("0123456789"));
    if (pos >= strlen(paddr) || pos >= sizeof(devName))
    {
        ACE_ERROR((LM_ERROR,
            "MELDeviceManager::parseAddress: Address length over %s.\n",
            paddr));
        return -1;
    }
    // Extract device type
    memmove(devName, paddr, pos);
    if (devName[pos - 1] == ':')
        devName[pos - 1] = '\0';
    else
        devName[pos] = '\0';

    PLCDevice * mem = this->get(devName);
    if (mem == NULL)
    {
        ACE_ERROR((LM_ERROR,
            ACE_TEXT("MELDeviceManager::parseAddress: Illegal device type (%s).\n"),
            devName));
        return -1;
    }
    // Convert address numerical value
    ULONG top = strtoul(paddr + pos, NULL, 0);
    if (top >= mem->size())
    {
        ACE_ERROR((LM_ERROR,
            ACE_TEXT("MELDeviceManager::parseAddress: Numeric is over (%s : %u).\n"),
            paddr, mem->size()));
        return -1;
    }
    paccess.set(mem->devCode(), top, mem);

    return 0;
}

//-----------------------------------------------------------------------------
// Set clock to plc
//-----------------------------------------------------------------------------
int MELDeviceManager::setClock(struct tm& newtm)
{
    u_short timebuf[16];
    PLCAddress address(MELDEV_D, 20);  // !!! CONSTANT ADDRESS !!!

    timebuf[0] = newtm.tm_year + 1900;
    timebuf[1] = newtm.tm_mon + 1;
    timebuf[2] = newtm.tm_mday;
    timebuf[3] = newtm.tm_hour;
    timebuf[4] = newtm.tm_min;
    timebuf[5] = newtm.tm_sec;
    timebuf[6] = newtm.tm_wday;

    MELDeviceManager::instance()->write(address, 7, timebuf);

    // PUT L270 (setting timer switch)
    PLCAddress timeAddress(MELDEV_L, 270);  // !!! CONSTANT ADDRESS !!!
    MELDeviceManager::instance()->put(timeAddress, 1);

    return 0;
}

//-----------------------------------------------------------------------------
// Get clock from plc
//-----------------------------------------------------------------------------
int MELDeviceManager::getClock(struct tm& rettm)
{
    unsigned short timebuf[16];
    PLCAddress address(MELDEV_D, 10);   // !!! CONSTANT ADDRESS !!!

    MELDeviceManager::instance()->read(address, 6, timebuf);
    rettm.tm_year = timebuf[0] - 1900;
    rettm.tm_mon = timebuf[1] - 1;
    rettm.tm_mday = timebuf[2];
    rettm.tm_hour = timebuf[3];
    rettm.tm_min = timebuf[4];
    rettm.tm_sec = timebuf[5];
    rettm.tm_wday = 0;

    return 0;
}


//------------------------------------------------------------------------------
// Get PLC error message of MELSEC.
//------------------------------------------------------------------------------
const ACE_TCHAR * MELDeviceManager::errmsg(int errcode) const
{
    ACE_UNUSED_ARG(errcode);
    ACE_TCHAR * result = ACE_TEXT("ERROR");
    return result;
}

//------------------------------------------------------------------------------
// Returns the pointer to the PLCDeviceManager class.
// The BEE_BUILD_DLL and BEE_Export directives are necessary as take care
// of exporting the function for Win32 platforms.
#if !defined(PLC_MELSEC_CLIENT)

extern "C" BEE_Export PLCDeviceManager * CreatePLC(void);

PLCDeviceManager * CreatePLC(void)
{
    PLCDeviceManager * pm = MELDeviceManager::instance();
    return pm;
}

#endif
