/* -----------------------------------------------------------------------------
The copyright in this software is being made available under the Clear BSD
License, included below. No patent rights, trademark rights and/or 
other Intellectual Property Rights other than the copyrights concerning 
the Software are granted under this license.

The Clear BSD License

Copyright (c) 2018-2026, Fraunhofer-Gesellschaft zur Förderung der angewandten Forschung e.V. & The VVdeC Authors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted (subject to the limitations in the disclaimer below) 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 copyright holder nor the names of its
     contributors may be used to endorse or promote products derived from this
     software without specific prior written permission.

NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
THIS LICENSE. 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 <sstream>
#include <vector>
#include <cstdlib>

#include "CommonDef.h"

#include "dtrace.h"

namespace vvdec
{

#if ENABLE_TRACING
CDTrace *g_trace_ctx = NULL;
#endif

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, const vstring& channel_names )
{
    if( filename )
        m_trace_file = fopen( filename, "w" );
    if( !m_trace_file )
    {
      m_error_code = Error::FileOpenFailed;
    }

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

CDTrace::CDTrace( const char *filename, const dtrace_channels_t& channels )
{
  if( filename )
    m_trace_file = fopen( filename, "w" );
  if( !m_trace_file )
  {
    m_error_code = Error::FileOpenFailed;
  }

  for( dtrace_channels_t::const_iterator ci = channels.cbegin(); ci != channels.cend(); ++ci )
  {
    deserializationTable[ci->channel_name] = ci->channel_number;
    chanRules.push_back( Channel() );
  }
}

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

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;
}

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 ); }

CDTrace::Error 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 Error::BadRule;

        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 Error::OK; // 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 Error::UnknownChannel;
    }

    return Error::OK;
}

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 != Error::OK )
  {
    if( m_error_code == Error::BadRule )
      str = ( " - DTrace ERROR: Add tracing rule failed: DECERR_DTRACE_BAD_RULE" );
    else if( m_error_code == Error::UnknownChannel )
      str = ( " - DTrace ERROR: Add tracing rule failed: DECERR_DTRACE_UNKNOWN_CHANNEL" );
    else if ( m_error_code == Error::FileOpenFailed )
      str = ( " - DTrace ERROR: failed to open trace file" );
    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;
}

}   // namespace vvdec
