/**
 * register a monitor 
 * this function is thread safe 
 * 
 */
void TrexWatchDog::register_monitor(TrexMonitor *monitor) {
    if (!m_enable){
        return;
    }

    /* critical section start */
    std::unique_lock<std::mutex> lock(m_lock);

    /* sanity - not a must but why not... */
    for (int i = 0; i < m_mon_count; i++) {
        if ( (monitor == m_monitors[i]) || (m_monitors[i]->get_tid() == pthread_self()) ) {
            std::stringstream ss;
            ss << "WATCHDOG: double register detected\n\n" << Backtrace();
            throw TrexException(ss.str());
        }
    }

    /* check capacity */
    if (m_mon_count == MAX_MONITORS) {
        std::stringstream ss;
        ss << "WATCHDOG: too many registered monitors\n\n" << Backtrace();
        throw TrexException(ss.str());
    }

    /* add monitor */
    m_monitors[m_mon_count++] = monitor;

    /* critical section end */
    lock.unlock();

}
bool
TrexDpPortEvent::on_core_reporting_in(int thread_id, bool status) {
    /* mark sure no double signal */
    if (m_signal.at(thread_id)) {
        std::stringstream err;
        err << "double signal detected on event id: " << m_event_id;
        throw TrexException(err.str());

    }

    /* mark */
    m_signal.at(thread_id) = true;
    m_pending_cnt--;

    /* if any core reported an error - mark as a failure */
    if (!status) {
        on_error(thread_id);
    }

    /* event occured */
    if (m_pending_cnt == 0) {
        on_event();
        return true;
    } else {
        return false;
    }
}
/**
 * split a specific stream's VM to multiple cores
 * number of cores is implied by the size of the vector
 *
 */
void
TrexVmSplitter::split(TrexStream *stream, std::vector<TrexStream *> core_streams) {

    /* nothing to do if no VM */
    if (stream->m_vm.is_vm_empty()) {
        return;
    }

    /* prepare some vars */
    m_dp_core_count = core_streams.size();
    m_core_streams  = &core_streams;
    m_stream        = stream;

    uint16_t cache_size=m_stream->m_cache_size;
    /* split the cache_size  */
    if (cache_size>0) {

        /* TBD need to check if we need to it is not too big from pool */
        if (cache_size > 10000) {
            throw TrexException("Cache is too big try to reduce it ");
        }

        /* split like variable splitters with leftovers */
        uint16_t cache_per_core = cache_size/m_dp_core_count;
        uint16_t leftover   = cache_size % m_dp_core_count;

        if (cache_per_core<1) {
            cache_per_core=1;
            leftover=0;
        }

        for (TrexStream *core_stream : *m_core_streams) {
            core_stream->m_cache_size = cache_per_core;
            if (leftover) {
                core_stream->m_cache_size+=1;
                leftover-=1;
            }
        }
    }


    /* if we cannot split - compile the main and duplicate */
    bool rc = split_internal();
    if (!rc) {

        /* compile the stream and simply clone it to all streams */
        m_stream->vm_compile();

        /* for every core - simply clone the DP object */
        for (TrexStream *core_stream : *m_core_streams) {
            core_stream->m_vm_dp = m_stream->m_vm_dp->clone();
        }

        /* no need for the reference stream DP object */
        delete m_stream->m_vm_dp;
        m_stream->m_vm_dp = NULL;
    }
}
void 
TrexDpPortEvents::destroy_event(int event_id) {
    TrexDpPortEvent *event = lookup(event_id);
    if (!event) {
        /* cannot find event */
        throw TrexException("internal error - cannot find event");
    }

    m_events.erase(event_id);
    delete event;
}
void TrexWatchDog::start() {

    if (!m_enable){
        return ;
    }

    m_active = true;
    m_thread = new std::thread(&TrexWatchDog::_main, this);
    if (!m_thread) {
        throw TrexException("unable to create watchdog thread");
    }
}
/**
 * for a single root we can until done or a loop detected
 * 
 * @author imarom (24-Nov-15)
 * 
 * @param root_stream_id 
 */
void
TrexStreamsGraph::generate_graph_for_one_root(uint32_t root_stream_id) {

    std::unordered_map<uint32_t, bool> loop_hash;
    std::stringstream ss;
    
    uint32_t stream_id = root_stream_id;
    double offset = 0;

    while (true) {
        const TrexStream *stream;
        
        /* fetch the stream from the hash - if it is not present, report an error */
        try {
            stream = m_streams_hash.at(stream_id);
        } catch (const std::out_of_range &e) {
            ss << "stream id " << stream_id << " does not exists";
            throw TrexException(ss.str());
        }

        /* add the node to the hash for loop detection */
        loop_hash[stream_id] = true;

        /* create the right rate events for the stream */
        add_rate_events_for_stream(offset, stream);

        /* do we have a next stream ? */
        if (stream->m_next_stream_id == -1) {
            break;
        }

        /* loop detection */
        auto search = loop_hash.find(stream->m_next_stream_id);
        if (search != loop_hash.end()) {
            m_graph_obj->on_loop_detection();
            break;
        }

        /* handle the next one */
        stream_id = stream->m_next_stream_id;
    }
}
bool
TrexVmSplitter::split_internal() {

    const StreamVmInstructionVar *split_instr = m_stream->m_vm.get_split_instruction();

    /* if no split instruction was specified - fall back*/
    if (split_instr == NULL) {
        return false;
    }

    if (split_instr->get_instruction_type() == StreamVmInstruction::itFLOW_MAN) {
        return split_by_flow_var( (const StreamVmInstructionFlowMan *)split_instr );

    } else if (split_instr->get_instruction_type() == StreamVmInstruction::itFLOW_CLIENT) {
        return split_by_flow_client_var( (const StreamVmInstructionFlowClient *)split_instr );

    } else {
        throw TrexException("VM splitter : cannot split by instruction which is not flow var or flow client var");
    }

}
void
TrexStreamsCompiler::err(const std::string &err) {
    throw TrexException("*** error: " + err);
}
void  StreamVm::err(const std::string &err){
    throw TrexException("*** error: " + err);
}