/**
 * Copyright - See the COPYRIGHT that is included with this distribution.
 * EPICS pvData is distributed subject to a Software License Agreement found
 * in file LICENSE that is included with this distribution.
 */
/**
 * @author mrk
 */
/* Marty Kraimer 2011.03 */
/* This connects to a DB record and presents the data as a PVStructure
 * It provides access to  value, alarm, display, and control.
 */

#include <cstddef>
#include <cstdlib>
#include <cstddef>
#include <string>
#include <cstdio>
#include <stdexcept>
#include <memory>

#include <dbAccess.h>
#include <dbEvent.h>
#include <dbNotify.h>
#include <dbCommon.h>

#include <pv/pvIntrospect.h>
#include <pv/pvData.h>
#include <pv/pvAccess.h>

#include "dbPv.h"
#include "dbUtil.h"

namespace epics { namespace pvaSrv { 

using namespace epics::pvData;
using namespace epics::pvAccess;

DbPvPut::DbPvPut(
    ChannelBase::shared_pointer const &dbPv,
    ChannelPutRequester::shared_pointer const &channelPutRequester,
    DbAddr &dbAddr)
: dbUtil(DbUtil::getDbUtil()),
  dbPv(dbPv),
  channelPutRequester(channelPutRequester),
  dbAddr(dbAddr),
  propertyMask(0),
  process(false),
  firstTime(true),
  beingDestroyed(false)
{
    if(DbPvDebug::getLevel()>0) printf("dbPvPut::dbPvPut()\n");
}

DbPvPut::~DbPvPut()
{
    if(DbPvDebug::getLevel()>0) printf("dbPvPut::~dbPvPut()\n");
}

bool DbPvPut::init(PVStructure::shared_pointer const &pvRequest)
{
    propertyMask = dbUtil->getProperties(
        channelPutRequester,
        pvRequest,
        dbAddr,
        true);
    if(propertyMask==dbUtil->noAccessBit) return false;
    if(propertyMask==dbUtil->noModBit) {
        channelPutRequester->message(
             String("field not allowed to be changed"),errorMessage);
        return 0;
    }
    pvStructure = PVStructure::shared_pointer(
        dbUtil->createPVStructure(
            channelPutRequester,
            propertyMask,
            dbAddr));
    if(pvStructure.get()==0) return 0;
    if((propertyMask&dbUtil->dbPutBit)!=0) {
        if((propertyMask&dbUtil->processBit)!=0) {
            channelPutRequester->message(
             String("process determined by dbPutField"),errorMessage);
        }
    } else if((propertyMask&dbUtil->processBit)!=0) {
       process = true;
       pNotify.reset(new (struct putNotify)());
       notifyAddr.reset(new DbAddr());
       memcpy(notifyAddr.get(),&dbAddr,sizeof(DbAddr));
       DbAddr *paddr = notifyAddr.get();
       struct dbCommon *precord = paddr->precord;
       char buffer[sizeof(precord->name) + 10];
       strcpy(buffer,precord->name);
       strcat(buffer,".PROC");
       if(dbNameToAddr(buffer,paddr)!=0) {
            throw std::logic_error(String("dbNameToAddr failed"));
       }
       struct putNotify *pn = pNotify.get();
       pn->userCallback = this->notifyCallback;
       pn->paddr = paddr;
       pn->nRequest = 1;
       pn->dbrType = DBR_CHAR;
       pn->usrPvt = this;
    }
    int numFields = pvStructure->getNumberFields();
    bitSet.reset(new BitSet(numFields));
    channelPutRequester->channelPutConnect(
       Status::Ok,
       getPtrSelf(),
       pvStructure,
       bitSet);
    return true;
}

String DbPvPut::getRequesterName() {
    return channelPutRequester->getRequesterName();
}

void DbPvPut::message(String const &message,MessageType messageType)
{
    channelPutRequester->message(message,messageType);
}

void DbPvPut::destroy() {
    if(DbPvDebug::getLevel()>0) printf("dbPvPut::destroy beingDestroyed %s\n",
         (beingDestroyed ? "true" : "false"));
    {
        Lock xx(mutex);
        if(beingDestroyed) return;
        beingDestroyed = true;
    }
    dbPv->removeChannelPut(getPtrSelf());
}

void DbPvPut::put(bool lastRequest)
{
    if(DbPvDebug::getLevel()>0) printf("dbPvPut::put()\n");
    Lock lock(dataMutex);
    PVFieldPtr pvField = pvStructure.get()->getPVFields()[0];
    if(propertyMask&dbUtil->dbPutBit) {
        Status status = dbUtil->putField(
            channelPutRequester,propertyMask,dbAddr,pvField);
        lock.unlock();
        channelPutRequester->putDone(status);
        if(lastRequest) destroy();
        return;
    }
    dbScanLock(dbAddr.precord);
    Status status = dbUtil->put(
        channelPutRequester,propertyMask,dbAddr,pvField);
    dbScanUnlock(dbAddr.precord);
    lock.unlock();
    if(process) {
        epicsUInt8 value = 1;
        pNotify.get()->pbuffer = &value;
        dbPutNotify(pNotify.get());
        event.wait();
    }
    channelPutRequester->putDone(status);
    if(lastRequest) destroy();
}

void DbPvPut::notifyCallback(struct putNotify *pn)
{
    DbPvPut * cput = static_cast<DbPvPut *>(pn->usrPvt);
    cput->event.signal();
}

void DbPvPut::get()
{
    if(DbPvDebug::getLevel()>0) printf("dbPvPut::get()\n");
    {
    Lock lock(dataMutex);
    bitSet->clear();
    dbScanLock(dbAddr.precord);
    Status status = dbUtil->get(
        channelPutRequester,
        propertyMask,dbAddr,
        pvStructure,
        bitSet,
        0);
    if(firstTime) {
        firstTime = false;
        bitSet->set(pvStructure->getFieldOffset());
    }
    dbScanUnlock(dbAddr.precord);
    }
    channelPutRequester->getDone(Status::Ok);
}

void DbPvPut::lock()
{
    dataMutex.lock();
}

void DbPvPut::unlock()
{
    dataMutex.unlock();
}

}}
