//Editor-Info: -*- C++ -*-
//
//Subject: TOVE project / pf
//
//File: debug.cpp
//
//Version: $Revision: 1.20 $
//
//State: $State: Exp $
//
//Date: $Date: 1998/12/16 16:58:56 $
//
//Organisation:
//      Helsinki University of Technology
//      Laboratory of Telecommunications Software and Multimedia
//
//Author:
//      Timo Kokkonen
//
//Description:
//      See corresponding header file.
//
//Copyright:
//      Copyright 1999 Helsinki University of Technology
//      ALL RIGHTS RESERVED BETWEEN JANUARY 1996 AND JUNE 1999.
//
//Licence:
//
//
//History:
//

#include "debug.h"
#include <iostream.h>
#include <ctype.h>

// for timestamp generating
#include <sys/time.h>

// For the file handling
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>

#include "types.h"

#ifndef NO_EVENT
// For Corba event
#include "pf/error.h"
#include <OB/CORBA.h>
#include <OB/Util.h>
#include <OB/CosEventChannelAdmin.h>
#endif

pfDebug *pfDebug :: _only = 0;

pfDebug :: pfDebug(void)
    : _output(_OUTPUT_NULL_),
      _logFile("pfdebug.log"),
#ifndef NO_EVENT
      _supplier(0),
      _eventServiceStr("DebugEventService"),
#endif
      _protocolCreated(" PROTOCOL CREATED: "),
      _protocolDeleted(" PROTOCOL DELETED: "),
      _debug("DEBUG:"),
      _change(" CHANGING_STATE: "),
      _ptr_r(" -> "),
      _receiv(" RECEIVING: "),
      _recvs(" receives "),
      _sendToA(" SENDING_TO_A: "),
      _sendToB(" SENDING_TO_B: "),
      _startRun(" START_RUNNING: "),
      _endRun(" END_RUNNING"),
      _runs(" runs "),
      _error("ERROR: "),
      _excepThrown("EXCEPTION_THROWN: "),
      _excepCatched("EXCEPTION_CATCHED: "),
      _trace(" TRACE: "),
      _pfUlong(" = pfUlong : "),
      _string(" = string : "),
      _pfFrame(" = pfFrame"),
      _eoFrame("END_OF_FRAME"),
      _timeStamp("TIMESTAMP: ")
{
    return;
}

pfDebug :: ~pfDebug(void)
{
    return;
}

pfDebug *pfDebug :: instance(void)
{
    if (_only == 0)
    {
        _only = new pfDebug();
    }
    return _only;
}

//----------------------------------------------------------------------

//
//Function: protocolCreated
//
//Description:
//      Inform about creating protocol.
//
void pfDebug :: protocolCreated(const pfProtocol *protocol_)
{
    string protocolName = getProtocolName(protocol_); 
    string debugInfo = _debug + _protocolCreated + protocolName;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: protocolDeleted
//
//Description:
//      Inform about deleting protocol.
//
void pfDebug :: protocolDeleted(const pfProtocol *protocol_)
{
    string protocolName = getProtocolName(protocol_); 
    string debugInfo = _debug + _protocolDeleted + protocolName;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: changingState
//
//Description:
//      Inform about state changes.
//
void pfDebug :: changingState(const pfState *oldState_, const pfState *newState_)
{
    string oldStateName = getStateName(oldState_);
    string newStateName = getStateName(newState_);

    string debugInfo = _debug + _change + oldStateName + _ptr_r + newStateName;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: receiving
//
//Description:
//      Inform about received events.
//
void pfDebug :: receiving(const pfProtocol *protocol_,
                          const pfTransporter *transporter_)
{
    string protocolName = getProtocolName(protocol_); 

    string transporterName = transporter_->getName();
    eraseNamesHead(transporterName);

    pfMessenger::SerialNumber serialNumber = transporter_->getSerialNumber();
    string serialNumberStr = serialnumberToString(serialNumber);

    string debugInfo = _debug + _receiv + protocolName + _recvs + transporterName
        + "_" + serialNumberStr;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: sendingToA
//
//Description:
//      Inform about sent events.
//
void pfDebug :: sendingToA(const pfTransporter *transporter_)
{
    string transporterName = transporter_->getName();
    eraseNamesHead(transporterName);

    pfMessenger::SerialNumber serialNumber = transporter_->getSerialNumber();
    string serialNumberStr = serialnumberToString(serialNumber);

    string debugInfo = _debug + _sendToA + transporterName + "_" + serialNumberStr;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: sendingToB
//
//Description:
//      Inform about sent events.
//
void pfDebug :: sendingToB(const pfTransporter *transporter_)
{
    string transporterName = transporter_->getName();
    eraseNamesHead(transporterName);

    pfMessenger::SerialNumber serialNumber = transporter_->getSerialNumber();
    string serialNumberStr = serialnumberToString(serialNumber);

    string debugInfo = _debug + _sendToB + transporterName + "_" + serialNumberStr;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: startRunning
//
//Description:
//
//
void pfDebug :: startRunning(const pfProtocol *protocol_,
                             const pfTransporter *transporter_)
{
    string protocolName = getProtocolName(protocol_); 

    string transporterName = transporter_->getName();
    eraseNamesHead(transporterName);

    pfMessenger::SerialNumber serialNumber = transporter_->getSerialNumber();
    string serialNumberStr = serialnumberToString(serialNumber);

    string debugInfo = _debug + _startRun + protocolName + _runs + transporterName
        + "_" + serialNumberStr;

    debugTime();
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: endRunning
//
//Description:
//
//
void pfDebug :: endRunning(void)
{
    string debugInfo = _debug + _endRun;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: exceptionThrown
//
//Description:
//
//
void pfDebug :: exceptionThrown(pfUlong exception_,
                                const string &file_,
                                pfUlong line_)
{
    string exceptionStr = serialnumberToString(exception_);
    string file_line = parseFileAndLine(file_, line_);

    string debugInfo = _error + _excepThrown + exceptionStr + file_line;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: exceptionCatched
//
//Description:
//
//
void pfDebug :: exceptionCatched(pfUlong exception_)
{
    string exceptionStr = serialnumberToString(exception_);

    string debugInfo = _error + _excepCatched + exceptionStr;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: traceUserProvided
//
//Description:
//      Method for user debug, prints text info.
//
void pfDebug :: traceUserProvided(const string &output_,
                                  const string &file_,
                                  pfUlong line_)
{
    string file_line = parseFileAndLine(file_, line_);

    string debugInfo = _debug + _trace + output_ + file_line;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: tracepfUlong
//
//Description:
//      Method for user debug, prints text and pfUlong's value.
//
void pfDebug :: tracePfUlong(const string &output_,
                             pfUlong value_,
                             const string &file_,
                             pfUlong line_)
{
    string valueStr = serialnumberToString(value_);
    string file_line = parseFileAndLine(file_, line_);
    
    string debugInfo = _debug + _trace + output_ + _pfUlong + valueStr + file_line;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: traceString
//
//Description:
//      Method for user debug, prints text and string's value.
//
void pfDebug :: traceString(const string &output_,
                            const string &value_,
                            const string &file_,
                            pfUlong line_)
{
    string file_line = parseFileAndLine(file_, line_);

    string debugInfo = _debug + _trace + output_ + _string + value_ + file_line;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: tracePfFrame
//
//Description:
//      Method for user debug, prints text and pfFrame's value.
//
void pfDebug :: tracePfFrame(const string &output_,
                             const pfFrame &frame_,
                             const string &file_,
                             pfUlong line_)
{
    string file_line = parseFileAndLine(file_, line_);
    pfUlong frameLength = frame_.length();
    string frameLengthStr = serialnumberToString(frameLength);

    string debugInfo = _debug + _trace + output_ + _pfFrame +"["
        + frameLengthStr + "]" + file_line;
    output(debugInfo);

    printFrame(frame_);

    debugInfo = _debug + _trace + _eoFrame;
    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: traceTimeStamp
//
//Description:
//      Prints timestamp.
//
void pfDebug :: traceTimeStamp(const string &file_, pfUlong line_)
{
    string file_line = parseFileAndLine(file_, line_);

    string currentTimeStr;
    time_t currentTime;
    struct timeval timeValue;
    struct timezone timeZ;
    time(&currentTime);

    currentTimeStr = ctime(&currentTime);
    // Removing '/n' end of currentTimeStr
    int length = currentTimeStr.length();
#ifndef NON_STD_STL
    currentTimeStr.erase((length-1), length);
#else
    currentTimeStr.remove((length-1), length);
#endif // NON_STD_STL

    gettimeofday(&timeValue, &timeZ);
    string timeSec = serialnumberToString(timeValue.tv_sec);
    string timeUsec = serialnumberToString(timeValue.tv_usec);

    string debugInfo = _debug + _trace + _timeStamp + currentTimeStr + " " + timeSec
        + " sec " + timeUsec + " usec" + file_line;

    output(debugInfo);

    return;
}

//----------------------------------------------------------------------

//
//Function: getStateName
//
//Description:
//      Gets state name using RTTI.
//
string pfDebug :: getStateName(const pfState *state_)
{
    string stateName("_UNKNOWN_STATE_");

    if (state_ != 0)
    {
        stateName = typeid(*state_).name ();

        eraseNamesHead(stateName);
    }

    return stateName;
}

//----------------------------------------------------------------------

//
//Function: getProtocolName
//
//Description:
//	Gets the name of the protocol object.
//
string pfDebug :: getProtocolName(const pfProtocol *protocol_)
{
    string result = typeid(*protocol_).name (); 
    eraseNamesHead(result);
    return result;
}

//----------------------------------------------------------------------

//
//Function: eraseNamesHead
//
//Description:
//      Method erase names head. Means that method cuts in string everything
//      before . and if there is any digits after after . they will removed 
//        e.g. xxx.6yyyzzz -> yyyzzz
//                 xxyyzz -> xxyyzz
//                .6xxyyzz -> xxyyzz
//                xxyyzz. ->        (empty)
//
void pfDebug :: eraseNamesHead(string &name_)
{
    string tmp("");
    pfUlong counter = 0;
    pfUlong nameLength = name_.length();

    if (nameLength > 0)
    {
        while ((name_[counter] != '.') && (counter < nameLength))
        {
            ++counter;
        }
        ++counter; // moves pointer over . -mark.

        // Skips possible digits after .
        while ((isdigit(name_[counter]) == 1) && (counter < nameLength))
        {
            ++counter;
        }

        if (counter <= nameLength)
        {
            while (counter < nameLength)
            {
                tmp.append(1, name_[counter]);
                ++counter;
            }
            name_ = tmp;
        }
    }
    return;
}

//----------------------------------------------------------------------

//
//Function: serialnumberToString
//
//Description:
//      Makes serialNumber to string. Can also use pfUlong to string.
//
string pfDebug :: serialnumberToString(pfMessenger::SerialNumber serialNumber_)
{
    string result("");

    if (serialNumber_ == 0)
    {
        result = (char)((serialNumber_ % 10) + 48) + result;
    }
    else
    {
        while (serialNumber_ > 0)
        {
            result = (char)((serialNumber_ % 10) + 48) + result;
            serialNumber_ /= 10;
        }
    }
    return result;
}

//----------------------------------------------------------------------

//
//Function: parseFileAndLine
//
//Description:
//      Parsing file name and line numbet to string. Parsed name look like
//      " : file : line".
//
string pfDebug :: parseFileAndLine(const string &file_, pfUlong line_)
{
    string result("");
    string lineStr = serialnumberToString(line_);

    result = " : " + file_ + " : " + lineStr;

    return result;
}

//----------------------------------------------------------------------

//
//Function: printFrame
//
//Description:
//      Prints frame to the outPut. Method prints frame information hex and ascii.
//      Method prints all in 8-bit, so e.g. 16 bit Ulong become two 8 bit Ulong.
//      output look like: 6F 67 78 4C 38 21 42 41 BC 31 : ogxL8!BAa1
//
void pfDebug :: printFrame(const pfFrame &frame_)
{
    string debugInfo;
    string hexString;
    string ascString;
    string temp;
    pfUlong tmp;
    pfUlong digitsInFrame = frame_.length();
    pfUlong counter = 0;
    pfUlong position = 0;

    while (digitsInFrame > 0)
    {
        ascString = "";
        hexString = "";

        // Read 10 8-bits char in frame to ascii and hex string.
        while ((counter < 10) && (digitsInFrame > 0))
        {
            tmp = frame_.read(position);
            // If char is printable mark
            if (isprint(tmp) != 0)
            {
                ascString.append(1, (char)tmp);
            }
            else
            {
                ascString.append(1, '.');
            }

            temp = pfUlongToHexStr(tmp);
            // If hex value is not two (it is one) mark long then add 0 at the first.
            if (temp.length() != 2)
            {
                temp = "0" + temp;
            }

            hexString += (temp + " ");

            ++counter;
            ++position;
            --digitsInFrame;
        }
        // Force hex string always the same length.
        while (counter < 10)
        {
            hexString += "   ";
            ++counter;
        }

        counter = 0;
        // One line is ready to print.
        debugInfo = _debug + _trace + hexString + ": " + ascString;

        output(debugInfo);
    }

    return;
}

//----------------------------------------------------------------------

//
//Function: ulongToHexStr
//
//Description:
//      Makes pfUlong to hex string.
//
string pfDebug :: pfUlongToHexStr(pfUlong value_)
{
    string result("");
    pfUlong tmp;

    do
    {
        tmp = value_ % 16;
        value_ /= 16;

        switch (tmp)
        {
            case 0:
                result = "0" + result;
                break;
            case 1:
                result = "1" + result;
                break;
            case 2:
                result = "2" + result;
                break;
            case 3:
                result = "3" + result;
                break;
            case 4:
                result = "4" + result;
                break;
            case 5:
                result = "5" + result;
                break;
            case 6:
                result = "6" + result;
                break;
            case 7:
                result = "7" + result;
                break;
            case 8:
                result = "8" + result;
                break;
            case 9:
                result = "9" + result;
                break;
            case 10:
                result = "A" + result;
                break;
            case 11:
                result = "B" + result;
                break;
            case 12:
                result = "C" + result;
                break;
            case 13:
                result = "D" + result;
                break;
            case 14:
                result = "E" + result;
                break;
            case 15:
                result = "F" + result;
                break;
            default:
                break;
        }
    }
    while (value_ > 0);

    return result;
}

//----------------------------------------------------------------------

//
//Function: output
//
//Description:
//      With this method can control class output information.
//      If __PF_DEBUG_OUTPUT_FILE__ is defined output is file (_logFile) and if
//      write to file fails output don't go anywere. Default output is cout.
//
void pfDebug :: output(const string &debugInfo_) const
{
    switch (_output)
    {
        case _OUTPUT_NULL_:
            break;
        case _OUTPUT_COUT_:
            cout << debugInfo_ << endl;
            break;
        case _OUTPUT_FILE_:
            {
                // flags write only, create if needeed, append end of file and block
                // other write.
                // modes needeed if file must create(owner rwx, group rx and other rx)
                int fd = open(_logFile, (O_WRONLY | O_CREAT | O_APPEND | O_SYNC),
                              (S_IRWXU | S_IRGRP |S_IXGRP | S_IROTH | S_IXOTH));

                if (fd != -1)
                {
                    write(fd, (char *)debugInfo_.c_str(), debugInfo_.length());
                    write(fd, "\n", 1);

                    close(fd);
                }
            }
            break;
        case _OUTPUT_CORBA_:
#ifndef NO_EVENT
            _supplier->send(debugInfo_);
#endif
            break;
        default:
            break;
    }

    return;
}

//----------------------------------------------------------------------

//
//Function: setOutputNull
//
//Description:
//      Sets output to null.
//
void pfDebug :: setOutputToNull(void)
{
#ifndef NO_EVENT
    if (_supplier != 0)
    {
        _supplier->disconnect_push_supplier();
    }
#endif
    _output = _OUTPUT_NULL_;

    return;
}

//----------------------------------------------------------------------

//
//Function: setOutputCout
//
//Description:
//      Sets output to cout.
//
void pfDebug :: setOutputToCout(void)
{
#ifndef NO_EVENT
    if (_supplier != 0)
    {
        _supplier->disconnect_push_supplier();
    }
#endif
    _output = _OUTPUT_COUT_;

    return;
}

//----------------------------------------------------------------------

//
//Function: setOutputFile
//
//Description:
//      Sets output to file.
//
void pfDebug :: setOutputToFile(void)
{
#ifndef NO_EVENT
    if (_supplier != 0)
    {
        _supplier->disconnect_push_supplier();
    }
#endif
    _output = _OUTPUT_FILE_;

    return;
}

//----------------------------------------------------------------------

//
//Function: setOutputCorba
//
//Description:
//      Sets output to corba.
//
void pfDebug :: setOutputToCorba(void)
{
#ifndef NO_EVENT
    initCorbaEvent();
    _output = _OUTPUT_CORBA_;
#endif

    return;
}

//----------------------------------------------------------------------

//
//Function: initCorbaEvent
//
//Description:
//      inits corba event output
//
void pfDebug :: initCorbaEvent(void)
{
#ifndef NO_EVENT
    CORBA_Object_var obj;

    try
    {
        obj = sfORBScheduler::getORB()->
            resolve_initial_references(_eventServiceStr.c_str());
    }

    catch (const CORBA_ORB::InvalidName&)
    {
        cerr << "can't resolve `EventService'" << endl;
        THROW_NETWORK_OUT_OF_ORDER;
    }
    catch(...)
    {
        cerr << "`DebugEventService' is a nil object reference" << endl;
        THROW_NETWORK_OUT_OF_ORDER;
    }

    CosEventChannelAdmin_EventChannel_var e =
	CosEventChannelAdmin_EventChannel::_narrow(obj);

    if (CORBA_is_nil(e))
    {
        cerr << "`DebugEventService' is not an EventChannel object reference" << endl;
        THROW_NETWORK_OUT_OF_ORDER;
    }

    //
    // Get ProxyPushConsumer
    //
    CosEventChannelAdmin_SupplierAdmin_ptr supplierAdmin =
	e->for_suppliers();
    CosEventChannelAdmin_ProxyPushConsumer_ptr consumer =
	supplierAdmin->obtain_push_consumer();

    //
    // Connect implementation to ProxyPushConsumer
    //
    _supplier = new PushSupplier_impl(consumer);
    consumer->connect_push_supplier(CosEventComm_PushSupplier::_nil());
#endif

    return;
}

//----------------------------------------------------------------------

