// Copyright (C) 2014-2017 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <vsomeip/constants.hpp>
#include <vsomeip/internal/logger.hpp>

#include "../include/channel_impl.hpp"
#include "../include/connector_impl.hpp"
#include "../include/defines.hpp"
#include "../../configuration/include/trace.hpp"
#include "../../utility/include/byteorder.hpp"

namespace vsomeip_v3 {
namespace trace {

const char *VSOMEIP_TC_DEFAULT_CHANNEL_ID = "TC";

std::shared_ptr<connector_impl> connector_impl::get() {
    static std::shared_ptr<connector_impl> instance = std::make_shared<connector_impl>();
    return instance;
}

connector_impl::connector_impl() :
    is_enabled_(false),
    is_sd_enabled_(false) {

    channels_[VSOMEIP_TC_DEFAULT_CHANNEL_ID]
        = std::make_shared<channel_impl>(VSOMEIP_TC_DEFAULT_CHANNEL_ID,
                                         VSOMEIP_TC_DEFAULT_CHANNEL_NAME);
#ifdef USE_DLT
    std::shared_ptr<DltContext> its_default_context
        = std::make_shared<DltContext>();

    contexts_[VSOMEIP_TC_DEFAULT_CHANNEL_ID] = its_default_context;
    DLT_REGISTER_CONTEXT_LL_TS(*(its_default_context.get()),
            VSOMEIP_TC_DEFAULT_CHANNEL_ID, VSOMEIP_TC_DEFAULT_CHANNEL_NAME,
            DLT_LOG_INFO, DLT_TRACE_STATUS_ON);
#endif
}

connector_impl::~connector_impl() {
    reset();
}

void connector_impl::configure(const std::shared_ptr<cfg::trace> &_configuration) {
    if (_configuration) {
        is_enabled_ = _configuration->is_enabled_;
        is_sd_enabled_ = _configuration->is_sd_enabled_;
    }

    if (is_enabled_) { // No need to create filters if tracing is disabled!
        for (auto &its_channel : _configuration->channels_) {
            if (!add_channel(its_channel->id_, its_channel->name_)) {
                VSOMEIP_ERROR << "Channel " << its_channel->id_
                                << " has multiple definitions.";
            }
        }

        for (auto &its_filter : _configuration->filters_) {
            for (auto &its_channel : its_filter->channels_) {
                std::shared_ptr<channel> its_channel_ptr = get_channel(its_channel);
                if (its_channel_ptr) {
                    if (its_filter->is_range_) {
                        its_channel_ptr->add_filter(its_filter->matches_[0],
                                its_filter->matches_[1], its_filter->is_positive_);
                    } else {
                        its_channel_ptr->add_filter(its_filter->matches_,
                                its_filter->is_positive_);
                    }
                }
            }
        }
    }

    VSOMEIP_INFO << "vsomeip tracing "
        << (is_enabled_ ? "enabled " : "not enabled. ")
        << ". vsomeip service discovery tracing "
        << (is_sd_enabled_ ? "enabled " : "not enabled. ");
}

void connector_impl::reset() {
#ifdef USE_DLT
    std::lock_guard<std::mutex> its_contexts_lock(contexts_mutex_);
    contexts_.clear();
#endif
    // reset to default
    std::lock_guard<std::mutex> its_lock_channels(channels_mutex_);
    channels_.clear();
}

void connector_impl::set_enabled(const bool _enabled) {
    is_enabled_ = _enabled;
}

bool connector_impl::is_enabled() const {
    return is_enabled_;
}

void connector_impl::set_sd_enabled(const bool _sd_enabled) {
    is_sd_enabled_ = _sd_enabled;
}

bool connector_impl::is_sd_enabled() const {
    return is_sd_enabled_;
}

bool connector_impl::is_sd_message(const byte_t *_data, uint16_t _data_size) const {
    if (VSOMEIP_METHOD_POS_MAX < _data_size) {
        return (_data[VSOMEIP_SERVICE_POS_MIN] == 0xFF && _data[VSOMEIP_SERVICE_POS_MAX] == 0xFF &&
                _data[VSOMEIP_METHOD_POS_MIN] == 0x81 && _data[VSOMEIP_METHOD_POS_MAX] == 0x00);
    }
    return false;
}

std::shared_ptr<channel> connector_impl::add_channel(
        const trace_channel_t &_id, const std::string &_name) {
    std::lock_guard<std::mutex> its_channels_lock(channels_mutex_);

    // check whether we already know the requested channel
    if (channels_.find(_id) != channels_.end())
        return nullptr;

    // create new channel
    std::shared_ptr<channel_impl> its_channel
        = std::make_shared<channel_impl>(_id, _name);

    // add channel
    channels_[_id] = its_channel;

    // register context
#ifdef USE_DLT
    std::lock_guard<std::mutex> its_contexts_lock(contexts_mutex_);
    std::shared_ptr<DltContext> its_context = std::make_shared<DltContext>();
    contexts_[_id] = its_context;
    DLT_REGISTER_CONTEXT_LL_TS(*(its_context.get()), _id.c_str(), _name.c_str(),
            DLT_LOG_INFO, DLT_TRACE_STATUS_ON);
#endif

    return its_channel;
}

bool connector_impl::remove_channel(const trace_channel_t &_id) {
    if (_id == VSOMEIP_TC_DEFAULT_CHANNEL_ID) {
        // the default channel can not be removed
        return false;
    }

    std::lock_guard<std::mutex> its_channels_lock(channels_mutex_);
    bool has_removed = (channels_.erase(_id) == 1);
    if (has_removed) {
        // unregister context
#ifdef USE_DLT
        std::lock_guard<std::mutex> its_contexts_lock(contexts_mutex_);
        auto its_context = contexts_.find(_id);
        if (its_context != contexts_.end()) {
            DLT_UNREGISTER_CONTEXT(*(its_context->second.get()));
        }
#endif
    }

    return true;
}

std::shared_ptr<channel> connector_impl::get_channel(const std::string &_id) const {
    std::lock_guard<std::mutex> its_channels_lock(channels_mutex_);
    auto its_channel = channels_.find(_id);
    return (its_channel != channels_.end() ? its_channel->second : nullptr);
}

void connector_impl::trace(const byte_t *_header, uint16_t _header_size,
        const byte_t *_data, uint16_t _data_size) {
#ifdef USE_DLT
    if (!is_enabled_)
        return;

    if (_data_size == 0)
        return; // no data

    if (is_sd_message(_data, _data_size) && !is_sd_enabled_)
        return; // tracing of service discovery messages is disabled!

    service_t its_service = VSOMEIP_BYTES_TO_WORD(
            _data[VSOMEIP_SERVICE_POS_MIN],
            _data[VSOMEIP_SERVICE_POS_MAX]);

    // Instance is not part of the SOME/IP header, read it from the trace
    // header
    instance_t its_instance = VSOMEIP_BYTES_TO_WORD(
            _header[VSOMEIP_TC_INSTANCE_POS_MIN],
            _header[VSOMEIP_TC_INSTANCE_POS_MAX]);

    method_t its_method = VSOMEIP_BYTES_TO_WORD(
            _data[VSOMEIP_METHOD_POS_MIN],
            _data[VSOMEIP_METHOD_POS_MAX]);

    // Forward to channel if the filter set of the channel allows
    std::lock_guard<std::mutex> its_channels_lock(channels_mutex_);
    std::lock_guard<std::mutex> its_contexts_lock(contexts_mutex_);
    for (auto its_channel : channels_) {
        if (its_channel.second->matches(its_service, its_instance, its_method)) {
            auto its_context = contexts_.find(its_channel.second->get_id());
            if (its_context != contexts_.end()) {
                DLT_TRACE_NETWORK_SEGMENTED(*(its_context->second.get()),
                    DLT_NW_TRACE_IPC,
                    _header_size, static_cast<void *>(const_cast<byte_t *>(_header)),
                    _data_size, static_cast<void *>(const_cast<byte_t *>(_data)));
            } else {
                // This should never happen!
                VSOMEIP_ERROR << "tracing: found channel without DLT context!";
            }
        }
    }
#else
    (void)_header;
    (void)_header_size;
    (void)_data;
    (void)_data_size;
#endif
}

} // namespace trace
} // namespace vsomeip_v3
