/* 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-2023, 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(nullptr), 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(nullptr), 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 K0149_BLOCK_STATISTICS
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
