/* The copyright in this software is being made available under the BSD
 * License, included below. This software may be subject to other third party
 * and contributor rights, including patent rights, and no such rights are
 * granted under this license.
 *
 * Copyright (c) 2010-2017, ITU/ISO/IEC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

/** \file     dtrace.cpp
 *  \brief    Implementation of trace messages support for debugging
 */

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <cstdlib>

#include "CommonDef.h"

#include "dtrace.h"
#include "dtrace_next.h"



void Channel::update( std::map< CType, int > state )
{

    for( std::list<Rule>::iterator rules_iter = rule_list.begin();
            rules_iter != rule_list.end();
            ++rules_iter ) {
        /* iterate over conditions, get the state of the condition type
         * and check if contion is met:
         *     if not -> go to next rule
         *        yes -> go to next condition
         * if all conditions are met: set channel active and return */
        bool probe = true;

        for( Rule::iterator cond_iter = rules_iter->begin();
                cond_iter != rules_iter->end();
                ++cond_iter ) {
            int sVal = state[cond_iter->type];
            if( !cond_iter->eval( cond_iter->rval, sVal ) ) {
                probe = false;
                break;
            }
        }
        if( probe ) {
            _active = true;
            return;
        }
    }

    _active = false;
}

void Channel::add( std::vector<Condition> rule )
{
    rule_list.push_back( rule );
}

static inline
std::vector<std::string> &split( const std::string &s, char delim, std::vector<std::string> &elems )
{
    std::stringstream ss( s );
    std::string item;
    while ( std::getline( ss, item, delim ) ) {
        elems.push_back( item );
    }
    return elems;
}

static inline
std::vector<std::string> split( const std::string &s, char delim )
{
    std::vector<std::string> elems;
    split( s, delim, elems );
    return elems;
}

CDTrace::CDTrace( const char *filename, vstring channel_names )
    : copy(false), m_trace_file(NULL), m_error_code( 0 )
{
    if( filename )
        m_trace_file = fopen( filename, "w" );

    int i = 0;
    for( vstring::iterator ci = channel_names.begin(); ci != channel_names.end(); ++ci ) {
        deserializationTable[*ci] = i++;
        chanRules.push_back( Channel() );
    }
}

CDTrace::CDTrace( const char *filename, const dtrace_channels_t& channels )
  : copy( false ), m_trace_file( NULL ), m_error_code( 0 )
{
  if( filename )
    m_trace_file = fopen( filename, "w" );

  //int i = 0;
  for( dtrace_channels_t::const_iterator ci = channels.begin(); ci != channels.end(); ++ci ) {
    deserializationTable[ci->channel_name] = ci->channel_number/*i++*/;
    chanRules.push_back( Channel() );
  }
}

CDTrace::CDTrace( const CDTrace& other )
{
    copy = true;
    m_trace_file         = other.m_trace_file;
    chanRules            = other.chanRules;
    condition_types      = other.condition_types;
    state                = other.state;
    deserializationTable = other.deserializationTable;
    m_error_code         = other.m_error_code;
}

CDTrace::CDTrace( const std::string& sTracingFile, const std::string& sTracingRule, const dtrace_channels_t& channels )
  : CDTrace( sTracingFile.c_str(), channels )
{
  //CDTrace::CDTrace( sTracingFile.c_str(), channels );
  if( !sTracingRule.empty() )
  {
    m_error_code = addRule( sTracingRule );
  }
}

void CDTrace::swap( CDTrace& other )
{
    using std::swap;
    CDTrace& first = *this;
    CDTrace& second = other;
    swap(first.copy,second.copy);
    swap(first.m_trace_file,second.m_trace_file);
    swap(first.chanRules,second.chanRules);
    swap(first.condition_types,second.condition_types);
    swap(first.state,second.state);
    swap(first.deserializationTable,second.deserializationTable);
}

CDTrace& CDTrace::operator=( const CDTrace& other )
{
    CDTrace tmp(other);
    swap( tmp );
    return *this;
}

CDTrace::~CDTrace()
{
    if( !copy && m_trace_file )
        fclose( m_trace_file );
}

bool _cf_eq ( int bound, int val ) { return ( val==bound ); }
bool _cf_neq( int bound, int val ) { return ( val!=bound ); }
bool _cf_le ( int bound, int val ) { return ( val<=bound ); }
bool _cf_ge ( int bound, int val ) { return ( val>=bound ); }

int CDTrace::addRule( std::string rulestring )
{
    vstring chans_conds = split( rulestring, ':' );
    vstring channels = split( chans_conds[0], ',' );
    vstring conditions = split( chans_conds[1], ',' );

    /* parse the rules first */
    std::vector<Condition> rule;
    for( vstring::iterator ci = conditions.begin(); ci != conditions.end(); ++ci ) {
        /* find one of "==", "!=", "<=", ">=" */
        const char *ops_[] = { "==", "!=", "<=", ">=" };
        vstring operators( ops_,&ops_[sizeof( ops_ )/sizeof( ops_[0] )] );
        vstring::iterator oi = operators.begin();
        std::size_t pos = std::string::npos;
        do {
            if( ( pos = ci->find( *oi ) ) != std::string::npos ) break;
        } while( ++oi != operators.end() );

        /* No operator found, malformed rules string -> abort */
        if( pos == std::string::npos ) return -2;

        CType ctype( *ci,0,pos );
        int value = std::atoi( ci->substr( pos+2, ci->length()-( pos+2 ) ).c_str() );
        //if( condition_types.find( ctype ) == condition_types.end() ) return 0;

        /* partially apply the condition value to the associated
         * condtion function and append it to the rule */
        bool ( *cfunc )( int,int );
        if( "==" == *oi ) cfunc = _cf_eq;
        else if( "!=" == *oi ) cfunc = _cf_neq;
        else if( "<=" == *oi ) cfunc = _cf_le;
        else if( ">=" == *oi ) cfunc = _cf_ge;
        else return 0; // this is already taken care of

        rule.push_back( Condition( ctype, cfunc, value ) );
    }

    /* add the rule to each channel */
    for( vstring::iterator chan_iter = channels.begin(); chan_iter != channels.end(); ++chan_iter ) {
        std::map< Key, int>::iterator ichan = deserializationTable.find(*chan_iter);
        if( ichan != deserializationTable.end() )
            chanRules[ichan->second].add( rule );
        else
            return -3;
    }

    //return (int)channels.size();
    return 0;
}

bool CDTrace::update( state_type stateval )
{
    state[stateval.first] = stateval.second;

    /* pass over all the channel rules */
    for( std::vector< Channel >::iterator citer = chanRules.begin(); citer != chanRules.end(); ++citer )
    {
        citer->update( state );
    }

    return true;
}

void CDTrace::getChannelsList( std::string& sChannels )
{
  sChannels.clear();
  /* pass over all the channel rules */
  if( deserializationTable.size() > 0 )
  {
    for( channel_map_t::iterator it = deserializationTable.begin(); it != deserializationTable.end(); ++it )
      sChannels += it->first + "\n";
  }
}

const char* CDTrace::getChannelName( int channel_number )
{
  static const char not_found[] = "";
  if( deserializationTable.size() > 0 )
  {
    for( channel_map_t::iterator it = deserializationTable.begin(); it != deserializationTable.end(); ++it )
      if( it->second == channel_number )
        return it->first.c_str();
  }
  return not_found;
}

std::string CDTrace::getErrMessage()
{
  std::string str = "";
  if( m_error_code )
  {
    if( m_error_code == -2 )
      str = ( " - DTrace ERROR: Add tracing rule failed: DECERR_DTRACE_BAD_RULE" );
    else if( m_error_code == -3 )
      str = ( " - DTrace ERROR: Add tracing rule failed: DECERR_DTRACE_UNKNOWN_CHANNEL" );
    else
    {
      str = " - DTrace ERROR: Undefined error";
    }
  }

  return str;
}

template< bool bCount>
void CDTrace::dtrace( int k, const char *format, /*va_list args*/... )
{
  if( m_trace_file && chanRules[k].active() )
  {
    va_list args;
    va_start ( args, format );
    vfprintf ( m_trace_file, format, args );
    fflush( m_trace_file );
    va_end ( args );
    if( bCount )
      chanRules[k].incrementCounter();
  }
  return;
}

template void CDTrace::dtrace<true>( int k, const char *format, /*va_list args*/... );
template void CDTrace::dtrace<false>( int k, const char *format, /*va_list args*/... );

void CDTrace::dtrace_repeat( int k, int i_times, const char *format, /*va_list args*/... )
{
  if( m_trace_file && chanRules[k].active() )
  {
    va_list args;
    va_start( args, format );
    while( i_times > 0 )
    {
      i_times--;
      vfprintf( m_trace_file, format, args );
    }
    fflush( m_trace_file );
    va_end( args );
  }
  return;
}

#if ENABLE_BLOCK_STATS
void CDTrace::dtrace_header( const char *format, /*va_list args*/... )
{
  if( m_trace_file )
  {
    va_list args;
    va_start ( args, format );
    vfprintf ( m_trace_file, format, args );
    fflush( m_trace_file );
    va_end ( args );
  }
  return;
}
#endif
