/* mesytec-mvlc - driver library for the Mesytec MVLC VME controller
 *
 * Copyright (C) 2020-2023 mesytec GmbH & Co. KG <info@mesytec.com>
 *
 * Author: Florian Lüke <f.lueke@mesytec.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
#ifndef __MESYTEC_MVLC_MVME_MVLC_UTIL_H__
#define __MESYTEC_MVLC_MVME_MVLC_UTIL_H__

#include <iomanip>
#include <optional>
#include <vector>

#include "mesytec-mvlc/mesytec-mvlc_export.h"
#include "mesytec-mvlc/mvlc_constants.h"
#include "mesytec-mvlc/util/fmt.h"

namespace mesytec
{
namespace mvlc
{

struct MESYTEC_MVLC_EXPORT FrameInfo
{
    u16 len;
    u8 type;
    u8 flags;
    u8 stack;
    u8 ctrl;
    u8 sysEventSubType;
};

inline FrameInfo extract_frame_info(u32 header)
{
    using namespace frame_headers;

    FrameInfo result = {};

    result.type = (header >> TypeShift) & TypeMask;
    result.len = (header >> LengthShift) & LengthMask;

    if (result.type == frame_headers::SystemEvent)
    {
        result.ctrl = (header >> system_event::CtrlIdShift) & system_event::CtrlIdMask;
        result.sysEventSubType = (header >> system_event::SubtypeShift) & system_event::SubtypeMask;
        result.flags = ((header >> system_event::ContinueShift) & system_event::ContinueMask)
                       << frame_flags::shifts::Continue;
    }
    else
    {
        result.flags = (header >> FrameFlagsShift) & FrameFlagsMask;
        result.stack = (header >> StackNumShift) & StackNumMask;
        result.ctrl = (header >> CtrlIdShift) & CtrlIdMask;
    }

    return result;
}

inline u8 extract_frame_flags(u32 header) { return extract_frame_info(header).flags; }

MESYTEC_MVLC_EXPORT std::string decode_frame_header(u32 header);
MESYTEC_MVLC_EXPORT std::string format_frame_flags(u8 frameFlags);

inline bool has_error_flag_set(u8 frameFlags)
{
    return (frameFlags & frame_flags::AllErrorFlags) != 0u;
}

inline u32 get_frame_length(u32 header) { return extract_frame_info(header).len; }

MESYTEC_MVLC_EXPORT const char *get_frame_flag_shift_name(u8 flag);

// String representation for the known system_event::subtype flags.
// Returns "unknown/custom" for user defined flags.
std::string MESYTEC_MVLC_EXPORT system_event_type_to_string(u8 eventType);

std::string MESYTEC_MVLC_EXPORT trigger_type_to_string(const u8 &tt);

std::string MESYTEC_MVLC_EXPORT trigger_subtype_to_string(const u8 &st);

std::string MESYTEC_MVLC_EXPORT trigger_to_string(const stacks::Trigger &trigger);

// Returns the VME IRQ value or nothing if the trigger is IRQ based. VME IRQ
// values are in the range [1, 7]. The MVLC has additional IRQs 8-16 which may
// also be returned by this function.
std::optional<int> MESYTEC_MVLC_EXPORT get_trigger_irq_value(const stacks::Trigger &trigger);
std::optional<int> MESYTEC_MVLC_EXPORT get_trigger_irq_value(const u16 triggerValue);

inline bool is_eth_header0(u32 header)
{
    return ((header >> eth::header0::MagicShift) & eth::header0::MagicMask) ==  eth::header0::MagicValue;
}

size_t MESYTEC_MVLC_EXPORT fixup_buffer_mvlc_usb(const u8 *buf, size_t bufUsed,
                                                 std::vector<u8> &tmpBuf);
size_t MESYTEC_MVLC_EXPORT fixup_buffer_mvlc_eth(const u8 *buf, size_t bufUsed,
                                                 std::vector<u8> &tmpBuf);

inline size_t fixup_buffer(ConnectionType bufferType, const u8 *msgBuf, size_t msgUsed,
                           std::vector<u8> &tmpBuf)
{
    if (bufferType == ConnectionType::ETH)
        return fixup_buffer_mvlc_eth(msgBuf, msgUsed, tmpBuf);

    return fixup_buffer_mvlc_usb(msgBuf, msgUsed, tmpBuf);
}

// Returns the number of bytes to skip ahead to get to the next frame header.
u32 skip_one_frame_usb(const std::basic_string_view<const u8> &view);
u32 skip_one_frame_eth(const std::basic_string_view<const u8> &view);

std::optional<ConnectionType> MESYTEC_MVLC_EXPORT detect_buffer_type(const u8 *buffer, size_t bufferBytes);
std::optional<ConnectionType> MESYTEC_MVLC_EXPORT detect_buffer_type(const u32 *buffer, size_t bufferWords);

// Returns the number of bytes that make up complete MVLC frames or UDP packets
// in the input buffer. So retval <= bufUsed. If no complete frame/packet is
// found, 0 is returned.
// This like the fixup_buffer* functions but instead of moving incomplete data
// it just returns the size of the complete frames.
size_t MESYTEC_MVLC_EXPORT calculate_complete_frames_bytes(ConnectionType bufferType, const u8 *buf, size_t bufUsed);
size_t MESYTEC_MVLC_EXPORT calculate_complete_frames_bytes(ConnectionType bufferType, std::basic_string_view<const u32> view);

bool MESYTEC_MVLC_EXPORT is_super_command(u32 dataWord);
bool MESYTEC_MVLC_EXPORT is_stack_command(u32 dataWord);

inline std::optional<super_commands::SuperCommandType> get_super_command_type(u32 dataWord)
{
    if (!is_super_command(dataWord))
        return std::nullopt;

    return static_cast<super_commands::SuperCommandType>(
        (dataWord >> super_commands::SuperCmdShift) & super_commands::SuperCmdMask);
}

inline std::optional<stack_commands::StackCommandType> get_stack_command_type(u32 dataWord)
{
    if (!is_stack_command(dataWord))
        return std::nullopt;

    return static_cast<stack_commands::StackCommandType>(
        (dataWord >> stack_commands::CmdShift) & stack_commands::CmdMask);
}

inline std::optional<u16> get_super_command_arg(u32 dataWord)
{
    if (!is_super_command(dataWord))
        return std::nullopt;

    return dataWord & super_commands::SuperCmdArgMask;
}

inline std::optional<u8> get_stack_command_arg0(u32 dataWord)
{
    if (!is_stack_command(dataWord))
        return std::nullopt;

    return (dataWord >> stack_commands::CmdArg0Shift) & stack_commands::CmdArg0Mask;
}

inline std::optional<u16> get_stack_command_arg1(u32 dataWord)
{
    if (!is_stack_command(dataWord))
        return std::nullopt;

    return (dataWord >> stack_commands::CmdArg1Shift) & stack_commands::CmdArg1Mask;
}

} // end namespace mvlc
} // end namespace mesytec

#endif /* __MESYTEC_MVLC_MVLC_UTIL_H__ */
