// $Id: MELDeviceServer.cpp,v 1.3 2002/10/12 09:11:53 fukasawa Exp $

//=============================================================================
/**
 *  @file    MELDeviceServer.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

#include "ace/Mem_Map.h"
#include "ace/Log_Msg.h"
#include "ace/Process_Semaphore.h"
#include "MELXmlParser.h"
#include "MELDeviceServer.h"

#define TEST_SERVICE   1

static const ACE_TCHAR * sema_name = ACE_TEXT("Logging_Semaphore");
static MELDeviceServer * _deviceServer = NULL;
static PLCTimerQueue     _timer_queue;    // The timer queue implementation.

// Function prototype
extern int RandomRead(long path, short stnum, short devp[], short bufp[],
                      short bufsize);

//------------------------------------------------------------------------------
//  Event Handler of Timer
//------------------------------------------------------------------------------
/**
 *  @class    TimerHandler
 *  @brief    Event handler for the timer queue timeout events.
 *  @note
 *  The <handle_timeout> hook method prints out the current
 * time, prints the time when this timer expired and deletes "this".
 */
class PLCTimerHandler : public ACE_Event_Handler
{
public:
    PLCTimerHandler(const ACE_Time_Value& expiration_time) :
            m_expires(expiration_time), m_id(0), m_sema(NULL), m_pulse(true) {}
    ~PLCTimerHandler(void) {}

    // Store an "id" for the Handler, which is only use to print better
    // messages.
    void set_id(int id) { this->m_id = id; }
    void pulse(bool tf) { this->m_pulse = tf; }
    void melserver(MELDeviceServer * melsvr) { this->m_melsvr = melsvr; }

    // Call back hook.
    virtual int handle_timeout(const ACE_Time_Value &current_time,
                               const void * arg);

    // The handler was cancelled, so we must delete this.
    virtual int cancelled(void) { delete this; return 0; }

    // Set semaphore
    void setSemaphore(ACE_SYNCH_PROCESS_SEMAPHORE * sema) { m_sema = sema; }

protected:
    // Store the expected time of expiration, it is used to print a nice
    // message saying how much delay was at the actual expiration time.
    ACE_Time_Value m_expires;

    // Store an "id" for the Handler, which is only use to print better
    // messages.
    int m_id;

    MELDeviceServer * m_melsvr;

    bool m_pulse;  // increment semaphore or not
    ACE_SYNCH_PROCESS_SEMAPHORE * m_sema;

};

//-----------------------------------------------------------------------------
// Transaction time out.
//-----------------------------------------------------------------------------

int PLCTimerHandler::handle_timeout(const ACE_Time_Value&, const void * arg)
{
    // Read data from melsec device memory.
    m_melsvr->project();

    // Notify update
    if (m_pulse)
    {
        m_sema->release();
    }

    return 0;
}

//-----------------------------------------------------------------------------
// Constructor/Destructor.
//-----------------------------------------------------------------------------
MELDeviceServer::~MELDeviceServer()
{
    if (m_randptr != NULL)
    {
        for (int i = 0; i < m_randq; i++)
        {
            free(*(m_randptr + i));
        }
        free(m_randptr);
    }
}

//------------------------------------------------------------------------------
// Instance MELSEC device server object.
//------------------------------------------------------------------------------
MELDeviceServer * MELDeviceServer::instance()
{
    if (_deviceServer != NULL) {
        return _deviceServer;
    }

    // Create melsec device server
    _deviceServer = new MELDeviceServer();

    MELDeviceManager::instance(_deviceServer);

    return _deviceServer;
}

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

    makeVirtualMemory();                // Create control header in memory

    setVirtualAddress();                // Set address to memory info.

    // Make random read parameters
    MELXmlParser * xmlParser = MELXmlParser::instance();
    int v100msec = xmlParser->cycle();
    m_period.set(v100msec / 10, (v100msec % 10) * 100000);

    result = initRandomReadInfo(xmlParser);

    return result;
}


//------------------------------------------------------------------------------
// Initialize virtual melsec memory.
//------------------------------------------------------------------------------
int MELDeviceServer::makeVirtualMemory()
{
    int    result;
    size_t total = sizeof(VirtualMelsec) +     // init device count
                   (sizeof(VirtualDevice) * (m_iodev.size() - 1));
    size_t hdsize = total;
    VirtualMelsec * vmtmp = (VirtualMelsec *)malloc(total);
    memset(vmtmp, 0, total);
    u_long curaddr = (u_long)total;
    int    realq = 0;
    for (u_int i = 0; i < m_iodev.size(); i++)
    {
        if (m_iodev[i] == NULL)
        {
            continue;
        }
        total += m_iodev[i]->bytes();
        vmtmp->m_vdevs[i].m_devcode = m_iodev[i]->devCode();
        vmtmp->m_vdevs[i].m_size = m_iodev[i]->words();
        vmtmp->m_vdevs[i].m_offset = curaddr;
        curaddr += vmtmp->m_vdevs[i].m_size;    // update offset address
        realq++;                                // count up device.
    }
    vmtmp->m_devq = realq;
    result = m_melmmap.map(m_filename.c_str(), total, O_RDWR | O_CREAT,
                           ACE_DEFAULT_FILE_PERMS, PROT_RDWR, ACE_MAP_SHARED);
    if (result == -1)
    {
        free(vmtmp);
        ACE_ERROR_RETURN((LM_ERROR,
            ACE_TEXT("MELDeviceManager::makeVirtualMemory: %p\n"),
            ACE_TEXT("mmap")), -1);
    }

    m_vmtop = (VirtualMelsec *)m_melmmap.addr();
    memmove(m_vmtop, vmtmp, hdsize);                        // copy header
    memset(((char *)m_vmtop + hdsize), 0, total - hdsize);  // clear data area.
    free(vmtmp);

    return result;
}


//------------------------------------------------------------------------------
// Enter Device Info to Manager Object.
//------------------------------------------------------------------------------
int MELDeviceServer::initRandomReadInfo(MELXmlParser * xmlParser)
{
    PLCDevice * plc;
    m_randq = xmlParser->m_readers.size();

//ACE_DEBUG((LM_DEBUG, ACE_TEXT("*** Random read count = %d ***\n"), m_randq));

    m_randptr = (RandomReadParam **)malloc(m_randq * sizeof(RandomReadParam *));
    for (int i = 0; i < m_randq; i++)
    {
        MelReadBlocks * melblks = &(xmlParser->m_readers[i]);
        size_t devnum = melblks->m_blocks.size();
        size_t psize = sizeof(RandomReadParam) +
                       (sizeof(ReadParam) * (devnum - 1));
        RandomReadParam * param = (RandomReadParam *)malloc(psize);
        param->m_blocks = melblks;
        param->m_devq = devnum;
        param->m_bufp = melblks->m_buftop;
        param->m_bufsize = melblks->m_words * 2;
        *(m_randptr + i) = param;

//ACE_DEBUG((LM_DEBUG, ACE_TEXT("    Random read (%d): num = %d, 0x%X\n"), i, devnum, melblks->m_buftop));

        for (u_int j = 0; j < devnum; j++)
        {
            plc = melblks->m_blocks[j].m_plc;
#if defined(PLC_SHARED_MEMORY)
            param->m_params[j].m_devid = plc->devCode();
            param->m_params[j].m_top = (u_short)melblks->m_blocks[j].m_top;
            param->m_params[j].m_dataq = melblks->m_blocks[j].m_datalen;
#else
#if defined(PLC_MELSEC_SERVER)            // real memory
            if (plc->devCode() == MELDEV_ZR)
            {
                if (melblks->m_blocks[j].m_top < 0x8000)
                {
                    // param->m_params[j].m_devid = MELDEV_R;
                    param->m_params[j].m_devid = MELDEV_ZR;
                    param->m_params[j].m_top = (u_short)melblks->m_blocks[j].m_top;
                }
                else
                {
                    param->m_params[j].m_devid =
                        (u_short)((MELDEV_ER - 1) + (melblks->m_blocks[j].m_top / 0x8000));
                    param->m_params[j].m_top = (u_short)melblks->m_blocks[j].m_top % 0x8000;
                }
                param->m_params[j].m_dataq = melblks->m_blocks[j].m_datalen;
            }
            else
#endif
            {
                param->m_params[j].m_devid = plc->devCode();
                param->m_params[j].m_top = (u_short)melblks->m_blocks[j].m_top;
                param->m_params[j].m_dataq = melblks->m_blocks[j].m_datalen;
            }
#endif
//ACE_DEBUG((LM_DEBUG,
//        ACE_TEXT("        Read block(%d): id = %d, top = 0x%X, dataq = %d\n"),
//            j, param->m_params[j].m_devid, param->m_params[j].m_top,
//            param->m_params[j].m_dataq));
        }
    }

    return 0;
}


//-----------------------------------------------------------------------------
// Spawn off a new thread.
//-----------------------------------------------------------------------------
int MELDeviceServer::open(void *)
{
    if (this->activate(THR_NEW_LWP | THR_DETACHED) == -1)
        ACE_ERROR_RETURN((LM_ERROR, ACE_TEXT("%p\n"), "spawn"), -1);

    if (_timer_queue.activate() == -1)
    {
        ACE_ERROR_RETURN((LM_ERROR,
                         ACE_TEXT("cannot activate timer queue.\n")), -1);
    }
    return 0;
}

//-----------------------------------------------------------------------------
int MELDeviceServer::close(u_long exit_status)
{
    ACE_DEBUG((LM_DEBUG,
              ACE_TEXT("(%t) thread is exiting with status %d in module %s\n"),
              exit_status, ACE_TEXT("MELDeviceServer")));
    return 0;
}

//-----------------------------------------------------------------------------
// Request to stop thread.
//-----------------------------------------------------------------------------
int MELDeviceServer::end()
{
    ACE_Message_Block * mb;
    ACE_NEW_RETURN(mb, ACE_Message_Block(8), -1);
    mb->length(0);
    if (this->putq(mb) == -1)
        ACE_ERROR((LM_ERROR, ACE_TEXT("(%t) %p\n"), ACE_TEXT("end")));

    ACE_OS::sleep(2);

    this->MELDeviceManager::fini();

    return 0;
}

//-----------------------------------------------------------------------------
// Simply enqueue the Message_Block into the end of the queue.
//-----------------------------------------------------------------------------
void MELDeviceServer::sema_onoff(bool onoff)
{
    if (m_timer)
        m_timer->pulse(onoff);

    return;
}

//-----------------------------------------------------------------------------
// Simply enqueue the Message_Block into the end of the queue.
//-----------------------------------------------------------------------------
int MELDeviceServer::put(ACE_Message_Block * mb, ACE_Time_Value * tv)
{
    return this->putq(mb, tv);
}

//-----------------------------------------------------------------------------
// Schedule a new timer.
//-----------------------------------------------------------------------------
int MELDeviceServer::addTimer(const ACE_Time_Value& interval,
                              PLCTimerHandler * tmh)
{
    ACE_Time_Value expire_at = ACE_OS::gettimeofday() + interval;
    int id = _timer_queue.schedule(tmh, 0, expire_at, interval);
    if (id == -1)
    {
        ACE_ERROR_RETURN((LM_ERROR, ACE_TEXT("Timer schedule failed")), -1);
    }
    // We store the id into the handler, this is only used to produce
    // nicer messages.
    tmh->set_id(id);
    tmh->melserver(this);

    ACE_DEBUG((LM_DEBUG,
              ACE_TEXT("Start timer(%d) : scheduling timer id = %d\n"),
              interval.msec(), id));
    return id;
}

//-----------------------------------------------------------------------------
// Cancel a timer.
//-----------------------------------------------------------------------------
int MELDeviceServer::cancelTimer(int timer_id)
{
    ACE_DEBUG((LM_DEBUG,
              ACE_TEXT("Cancel timer : scheduling timer id = %d\n"),
              timer_id));
    if (timer_id < 0)
    {
        return -1;
    }
    return _timer_queue.cancel(timer_id);
}


//-----------------------------------------------------------------------------
// Random read.
//-----------------------------------------------------------------------------
int MELDeviceServer::project()
{

    ACE_DEBUG((LM_DEBUG, ACE_TEXT("Projector start(%T) : ")));

    PLCDevice * plc;
    for (int i = 0; i < m_randq; i++)
    {
        RandomReadParam * param = *(m_randptr + i);

        if (param != NULL && param->m_params[0].m_devid != MELDEV_ZR)
        {
            int result = RandomRead(m_path, m_stationNum, (short *)&param->m_devq,
                                    (short *)param->m_bufp, param->m_bufsize);
            if (result != 0)
            {
                // ignore error
            }
        }

        MelReadBlocks * melblks = param->m_blocks;

        // Copy from buffer to shared memory
        for (u_int j = 0; j < melblks->m_blocks.size(); j++)
        {
            if (param->m_params[j].m_devid == MELDEV_ZR)
            {
                PLCDevice * r_plc = MELDeviceServer::instance()->get("R");
                plc->vmread(param->m_params[j].m_top, param->m_params[j].m_dataq,
                            melblks->m_blocks[j].m_bufp);
            }

            plc = melblks->m_blocks[j].m_plc;
            plc->setvm(param->m_params[j].m_top, param->m_params[j].m_dataq,
                       melblks->m_blocks[j].m_bufp);
        }
    }

    ACE_DEBUG((LM_DEBUG, ACE_TEXT("Projector end(%T).\n")));

    return 0;
}


//-----------------------------------------------------------------------------
// PLC Manege thread
//-----------------------------------------------------------------------------
int MELDeviceServer::svc(void)
{
    ACE_DEBUG((LM_DEBUG, "MELDeviceServer::svc start.\n"));

    ACE_SYNCH_PROCESS_SEMAPHORE sema(0, sema_name);

    int timer_id;
    if (m_randq > 0)
    {
        m_timer = new PLCTimerHandler(m_period);
        timer_id = this->addTimer(m_period, m_timer);
        m_timer->setSemaphore(&sema);
    }

    int result = 0;
    ACE_Message_Block * mb;

    for (;;)
    {
        result = this->getq(mb);
        if (result == -1)
            break;

        int length = mb->length();
        if (length > 0)
        {
            char * datap = mb->rd_ptr();

            //
            // Parse command
            //
        }
        mb->release();

        if (length == 0)             // shutdown
            break;
    }

    this->cancelTimer(timer_id);

    if (result == -1 && errno == EWOULDBLOCK)
    {
        ACE_ERROR((LM_ERROR,
           ACE_TEXT("(%t) %p\n"), ACE_TEXT("timed out waiting for message")));
    }

    sema.remove();
    delete m_timer;
    return 0;
}

