#include "pch.h"
#include "XYParserApi.h"
#include "XYImpedanceProcessor.h"
#include "XYWelchProcessor.h"

#include "XYEegParser8.h"
#include "XYEegParser64.h"

#include <algorithm>
#include <array>
#include <cstring>
#include <new>
#include <string>
#include <vector>

namespace {

constexpr std::uint8_t kCommandFrameHeader = 0xAA;
constexpr std::uint8_t kCommandFrameTail = 0x55;
constexpr std::size_t k8ChImpedanceCommandSize = 7;
constexpr std::size_t kTriggerPayloadSize = 3;
constexpr std::size_t kTriggerCommandSize = 1 + kTriggerPayloadSize + 1 + 2;
constexpr int k8ChLeadCount = 8;
constexpr int k64ChLeadCount = 64;
constexpr std::uint8_t kAlgorithmChannelCount = 64;
constexpr std::size_t kAlgorithmSampleByteCount =
    static_cast<std::size_t>(XYPARSER_FRAME_DATA_COLUMN_COUNT) * sizeof(double);

struct AlgorithmSample {
    std::array<double, XYPARSER_MAX_CHANNELS> channel_values_uv{};
    std::uint8_t trigger_type = 0;
    std::uint8_t trigger_index = 0;
};

std::uint8_t CalculateChecksum(const std::uint8_t* data, std::size_t size)
{
    std::uint8_t checksum = 0;
    for (std::size_t i = 0; i < size; ++i) {
        checksum = static_cast<std::uint8_t>(checksum + data[i]);
    }
    return checksum;
}

std::array<std::uint8_t, k8ChImpedanceCommandSize> Build8ChImpedanceCommand(bool open)
{
    const std::uint8_t payload = open ? XYPARSER_8CH_IMPEDANCE_OPEN : XYPARSER_8CH_IMPEDANCE_CLOSE;
    return {kCommandFrameHeader,
            0x00,
            0x01,
            payload,
            CalculateChecksum(&payload, 1),
            kCommandFrameTail,
            kCommandFrameTail};
}

bool IsSupportedTriggerType(std::uint8_t trigger_type)
{
    switch (trigger_type) {
    case XYPARSER_TRIGGER_NONE:
    case XYPARSER_TRIGGER_TRAIN_0:
    case XYPARSER_TRIGGER_TRAIN_1:
        return true;
    default:
        return false;
    }
}

std::array<std::uint8_t, kTriggerCommandSize> BuildTriggerCommand(std::uint8_t trigger_type)
{
    const std::array<std::uint8_t, kTriggerPayloadSize> payload = {0x00, 0x00, trigger_type};
    return {kCommandFrameHeader,
            payload[0],
            payload[1],
            payload[2],
            CalculateChecksum(payload.data(), payload.size()),
            kCommandFrameTail,
            kCommandFrameTail};
}

constexpr std::array<XYParserLeadChannelNumber, k8ChLeadCount> k8ChLeadMap = {
    LeadChannel_PO5, LeadChannel_POZ, LeadChannel_PO6, LeadChannel_PO7,
    LeadChannel_O1, LeadChannel_OZ, LeadChannel_O2, LeadChannel_PO8};

constexpr std::array<XYParserLeadChannelNumber, k64ChLeadCount> k64ChLeadMap = {
    LeadChannel_FP1, LeadChannel_FP2, LeadChannel_PO6, LeadChannel_POZ,
    LeadChannel_F3, LeadChannel_F4, LeadChannel_FPZ, LeadChannel_AF4,
    LeadChannel_FC3, LeadChannel_PO8, LeadChannel_CP2, LeadChannel_CP1,
    LeadChannel_FCZ, LeadChannel_PO5, LeadChannel_FC2, LeadChannel_FC1,
    LeadChannel_C3, LeadChannel_C4, LeadChannel_FC4, LeadChannel_CP4,
    LeadChannel_P3, LeadChannel_P4, LeadChannel_F5, LeadChannel_C5,
    LeadChannel_F6, LeadChannel_PO4, LeadChannel_CP6, LeadChannel_CP5,
    LeadChannel_PO3, LeadChannel_CP3, LeadChannel_FC6, LeadChannel_FC5,
    LeadChannel_CB1, LeadChannel_CB2, LeadChannel_P5, LeadChannel_AF7,
    LeadChannel_A1, LeadChannel_T7, LeadChannel_FT7, LeadChannel_TP7,
    LeadChannel_FT8, LeadChannel_AF8, LeadChannel_F8, LeadChannel_F7,
    LeadChannel_P6, LeadChannel_C6, LeadChannel_O2, LeadChannel_O1,
    LeadChannel_T8, LeadChannel_P7, LeadChannel_CZ, LeadChannel_PZ,
    LeadChannel_P8, LeadChannel_FZ, LeadChannel_OZ, LeadChannel_PO7,
    LeadChannel_TP8, LeadChannel_AF3, LeadChannel_C2, LeadChannel_C1,
    LeadChannel_P2, LeadChannel_P1, LeadChannel_F2, LeadChannel_F1};

struct ParserContext {
    std::uint8_t channel_count = 0;
    XYEegParser8 parser8;
    XYEegParser64 parser64;
    XYImpedanceProcessor impedance_processor;
    XYWelchProcessor welch_processor;
    double adc_vref = 4.5;
    double adc_gain = 6.0;
    std::vector<std::uint8_t> algorithm_byte_cache;
    std::vector<AlgorithmSample> algorithm_sample_cache;
    std::uint32_t next_algorithm_frame_index = 1;
    std::string last_error;
};

struct WelchBandInfo {
    const char* name;
    double low_hz;
    double high_hz;
};

constexpr std::array<WelchBandInfo, XYPARSER_WELCH_BAND_COUNT> kWelchBandInfos = {{
    {"delta", 0.5, 4.0},
    {"theta", 4.0, 8.0},
    {"alpha", 8.0, 13.0},
    {"beta", 13.0, 30.0},
    {"gamma", 30.0, 50.0},
}};

void FillSummary(const XYEegFrame8& frame, XYParserFrameSummary& summary)
{
    summary.frame_index = frame.index;
    summary.channel_count = frame.channel_count;
    summary.battery = frame.battery;
    summary.sample_count = static_cast<std::uint8_t>(frame.samples.size());
    summary.impedance_enabled = frame.impedance_enabled;
    summary.current_gain = frame.current_gain;
    summary.current_sample_rate_hz = frame.current_sample_rate_hz;
    summary.cap_type = frame.cap_type;
    summary.gnd_detached = frame.gnd_detached;
    for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
        summary.sample_trigger_types[sample_index] = frame.samples[sample_index].trigger_type;
        summary.sample_trigger_indices[sample_index] = frame.samples[sample_index].trigger_index;
        for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
            summary.channel_values_uv[sample_index][channel_index] =
                channel_index < frame.channel_count
                    ? frame.samples[sample_index].channel_values_uv[channel_index]
                    : 0.0;
        }
    }
}

void FillSummary(const XYEegFrame64& frame, XYParserFrameSummary& summary)
{
    summary.frame_index = frame.index;
    summary.channel_count = frame.channel_count;
    summary.battery = frame.battery;
    summary.sample_count = static_cast<std::uint8_t>(frame.samples.size());
    summary.impedance_enabled = frame.impedance_enabled;
    summary.current_gain = frame.current_gain;
    summary.current_sample_rate_hz = frame.current_sample_rate_hz;
    summary.cap_type = frame.cap_type;
    summary.gnd_detached = frame.gnd_detached;
    for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
        summary.sample_trigger_types[sample_index] = frame.samples[sample_index].trigger_type;
        summary.sample_trigger_indices[sample_index] = frame.samples[sample_index].trigger_index;
        for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
            summary.channel_values_uv[sample_index][channel_index] =
                channel_index < frame.channel_count
                    ? frame.samples[sample_index].channel_values_uv[channel_index]
                    : 0.0;
        }
    }
}

void Convert8ChSummaryTo64ChSummary(const XYParserFrameSummary& input_summary,
                                    XYParserFrameSummary& output_summary)
{
    std::memset(&output_summary, 0, sizeof(output_summary));
    output_summary.frame_index = input_summary.frame_index;
    output_summary.channel_count = 64;
    output_summary.battery = input_summary.battery;
    output_summary.sample_count = input_summary.sample_count;
    output_summary.impedance_enabled = input_summary.impedance_enabled;
    output_summary.current_gain = input_summary.current_gain;
    output_summary.current_sample_rate_hz = input_summary.current_sample_rate_hz;
    output_summary.cap_type = input_summary.cap_type;
    output_summary.gnd_detached = input_summary.gnd_detached;

    for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
        output_summary.sample_trigger_types[sample_index] = input_summary.sample_trigger_types[sample_index];
        output_summary.sample_trigger_indices[sample_index] = input_summary.sample_trigger_indices[sample_index];

        for (std::size_t eight_channel_index = 0; eight_channel_index < k8ChLeadMap.size(); ++eight_channel_index) {
            const XYParserLeadChannelNumber lead = k8ChLeadMap[eight_channel_index];
            output_summary.channel_values_uv[sample_index][static_cast<std::size_t>(lead)] =
                input_summary.channel_values_uv[sample_index][eight_channel_index];
        }
    }
}

void Convert64ChSummaryToAlgorithmData(const XYParserFrameSummary& input_summary,
                                       double* output_data)
{
    std::fill(output_data,
              output_data + XYPARSER_FRAME_ALGORITHM_VALUE_COUNT,
              0.0);
    for (std::size_t sample_index = 0; sample_index < XYPARSER_SAMPLES_PER_FRAME; ++sample_index) {
        const std::size_t sample_offset = sample_index * XYPARSER_FRAME_DATA_COLUMN_COUNT;
        for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
            output_data[sample_offset + channel_index] =
                input_summary.channel_values_uv[sample_index][channel_index];
        }
        output_data[sample_offset + XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX] =
            static_cast<double>(input_summary.sample_trigger_types[sample_index]);
        output_data[sample_offset + XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX] =
            static_cast<double>(input_summary.sample_trigger_indices[sample_index]);
    }
}

void FillSummaryFromAlgorithmSamples(const std::vector<AlgorithmSample>& samples,
                                     std::uint32_t frame_index,
                                     XYParserFrameSummary& summary)
{
    std::memset(&summary, 0, sizeof(summary));
    summary.frame_index = frame_index;
    summary.channel_count = kAlgorithmChannelCount;
    summary.sample_count = static_cast<std::uint8_t>(
        std::min<std::size_t>(samples.size(), static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME)));

    for (std::size_t sample_index = 0; sample_index < samples.size() &&
                                     sample_index < static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME);
         ++sample_index) {
        summary.sample_trigger_types[sample_index] = samples[sample_index].trigger_type;
        summary.sample_trigger_indices[sample_index] = samples[sample_index].trigger_index;
        for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
            summary.channel_values_uv[sample_index][channel_index] = samples[sample_index].channel_values_uv[channel_index];
        }
    }
}

bool CopyCommandBytes(const std::vector<std::uint8_t>& command,
                      std::uint8_t* out_buffer,
                      std::size_t buffer_size)
{
    if (out_buffer == nullptr || buffer_size < command.size()) {
        return false;
    }

    std::memcpy(out_buffer, command.data(), command.size());
    return true;
}

bool TryMapSampleRate(std::uint16_t sample_rate, XYEegParser64::SampleRateSwitch& mapped_sample_rate)
{
    switch (sample_rate) {
    case 250:
        mapped_sample_rate = XYEegParser64::SampleRateSwitch::k250;
        return true;
    case 500:
        mapped_sample_rate = XYEegParser64::SampleRateSwitch::k500;
        return true;
    case 1000:
        mapped_sample_rate = XYEegParser64::SampleRateSwitch::k1000;
        return true;
    case 2000:
        mapped_sample_rate = XYEegParser64::SampleRateSwitch::k2000;
        return true;
    default:
        return false;
    }
}

bool TryMapGain(std::uint8_t gain, XYEegParser64::GainSwitch& mapped_gain)
{
    switch (gain) {
    case 1:
        mapped_gain = XYEegParser64::GainSwitch::k1;
        return true;
    case 2:
        mapped_gain = XYEegParser64::GainSwitch::k2;
        return true;
    case 3:
        mapped_gain = XYEegParser64::GainSwitch::k3;
        return true;
    case 4:
        mapped_gain = XYEegParser64::GainSwitch::k4;
        return true;
    case 6:
        mapped_gain = XYEegParser64::GainSwitch::k6;
        return true;
    case 8:
        mapped_gain = XYEegParser64::GainSwitch::k8;
        return true;
    case 12:
        mapped_gain = XYEegParser64::GainSwitch::k12;
        return true;
    case 24:
        mapped_gain = XYEegParser64::GainSwitch::k24;
        return true;
    default:
        return false;
    }
}

int GetLeadMapCount(std::uint8_t channel_count)
{
    switch (channel_count) {
    case 8:
        return k8ChLeadCount;
    case 64:
        return k64ChLeadCount;
    default:
        return 0;
    }
}

} // namespace

extern "C" {

XYParserHandle XYParser_CreateParser(std::uint8_t channel_count)
{
    if (channel_count != 8 && channel_count != 64) {
        return nullptr;
    }

    ParserContext* context = new (std::nothrow) ParserContext();
    if (context == nullptr) {
        return nullptr;
    }

    context->channel_count = channel_count;
    context->impedance_processor.SetChannelCount(channel_count);
    context->welch_processor.SetChannelCount(channel_count);
    return context;
}

void XYParser_DestroyParser(XYParserHandle handle)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    delete context;
}

void XYParser_SetAdcParams(XYParserHandle handle, double vref, double gain)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return;
    }

    if (vref > 0.0) {
        context->adc_vref = vref;
    }
    if (gain > 0.0) {
        context->adc_gain = gain;
    }

    if (context->channel_count == 8) {
        context->parser8.SetAdcParams(vref, gain);
    } else {
        context->parser64.SetAdcParams(vref, gain);
    }
}

void XYParser_SetBypassChecksum(XYParserHandle handle, int bypass)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return;
    }

    const bool enabled = bypass != 0;
    if (context->channel_count == 8) {
        context->parser8.SetBypassChecksum(enabled);
    } else {
        context->parser64.SetBypassChecksum(enabled);
    }
}

void XYParser_SetSampleRate(XYParserHandle handle, int sample_rate)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return;
    }

    context->impedance_processor.SetSampleRate(sample_rate);
    context->welch_processor.SetSampleRate(sample_rate);
}

void XYParser_SetImpedanceDetection(XYParserHandle handle, int enabled)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return;
    }

    const bool impedance_enabled = enabled != 0;
    const double impedance_gain = impedance_enabled ? 24.0 : 6.0;
    context->adc_gain = impedance_gain;

    if (context->channel_count == 8) {
        context->parser8.SetAdcParams(context->adc_vref, impedance_gain);
    } else {
        context->parser64.SetAdcParams(context->adc_vref, impedance_gain);
    }

    context->impedance_processor.SetEnabled(impedance_enabled);
}

void XYParser_SetWelchDetection(XYParserHandle handle, int enabled)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return;
    }

    context->welch_processor.SetEnabled(enabled != 0);
}

std::size_t XYParser_Get64ImpedanceCommandSize(void)
{
    return XYEegParser64::kImpedanceCommandSize;
}

std::size_t XYParser_GetTriggerCommandSize(void)
{
    return kTriggerCommandSize;
}

int XYParser_SerializeTriggerCommand(std::uint8_t trigger_type,
                                     std::uint8_t* out_command,
                                     std::size_t command_size)
{
    if (out_command == nullptr || command_size < kTriggerCommandSize || !IsSupportedTriggerType(trigger_type)) {
        return 0;
    }

    const auto command = BuildTriggerCommand(trigger_type);
    std::copy(command.begin(), command.end(), out_command);
    return static_cast<int>(command.size());
}

std::size_t XYParser_Get64GainSampleRateCommandSize(void)
{
    return XYEegParser64::kGainSampleRateCommandSize;
}

int XYParser_Serialize64ImpedanceCommand(int open,
                                         std::uint8_t* out_buffer,
                                         std::size_t buffer_size)
{
    const auto command = XYEegParser64::SerializeImpedanceCommand(
        open != 0 ? XYEegParser64::ImpedanceSwitch::kOpen : XYEegParser64::ImpedanceSwitch::kClose);
    if (!CopyCommandBytes(command, out_buffer, buffer_size)) {
        return 0;
    }
    return static_cast<int>(command.size());
}

int XYParser_Serialize64GainSampleRateCommand(std::uint8_t gain,
                                              std::uint16_t sample_rate,
                                              std::uint8_t* out_buffer,
                                              std::size_t buffer_size)
{
    XYEegParser64::GainSwitch mapped_gain{};
    XYEegParser64::SampleRateSwitch mapped_sample_rate{};
    if (!TryMapGain(gain, mapped_gain) || !TryMapSampleRate(sample_rate, mapped_sample_rate)) {
        return 0;
    }

    const auto command = XYEegParser64::SerializeGainAndSampleRateCommand(mapped_gain, mapped_sample_rate);
    if (!CopyCommandBytes(command, out_buffer, buffer_size)) {
        return 0;
    }
    return static_cast<int>(command.size());
}

int XYParser_Feed(XYParserHandle handle,
                  const std::uint8_t* data,
                  std::size_t size,
                  XYParserFrameSummary* out_summaries,
                  int max_summaries)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr || data == nullptr || size == 0) {
        return 0;
    }

    if (max_summaries < 0) {
        max_summaries = 0;
    }

    if (context->channel_count == 8) {
        const std::vector<XYEegFrame8> frames = context->parser8.Feed(data, size);
        context->last_error = context->parser8.LastError();
        const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries);
        for (std::size_t i = 0; i < frames.size(); ++i) {
            XYParserFrameSummary summary{};
            FillSummary(frames[i], summary);
            context->impedance_processor.PushFrame(summary);
            if (static_cast<int>(i) < write_count) {
                out_summaries[static_cast<int>(i)] = summary;
            }
        }
        return write_count;
    }

    const std::vector<XYEegFrame64> frames = context->parser64.Feed(data, size);
    context->last_error = context->parser64.LastError();
    const int write_count = std::min<int>(static_cast<int>(frames.size()), max_summaries);
    for (std::size_t i = 0; i < frames.size(); ++i) {
        XYParserFrameSummary summary{};
        FillSummary(frames[i], summary);
        context->impedance_processor.PushFrame(summary);
        if (static_cast<int>(i) < write_count) {
            out_summaries[static_cast<int>(i)] = summary;
        }
    }
    return write_count;
}

int XYParser_Convert8ChFramesTo64Ch(const XYParserFrameSummary* input_summaries,
                                    int input_count,
                                    XYParserFrameSummary* output_summaries,
                                    int max_summaries)
{
    if (input_summaries == nullptr || output_summaries == nullptr || input_count <= 0 || max_summaries <= 0) {
        return 0;
    }

    const int convert_count = input_count < max_summaries ? input_count : max_summaries;
    for (int i = 0; i < convert_count; ++i) {
        if (input_summaries[i].channel_count != 8) {
            return i;
        }
        Convert8ChSummaryTo64ChSummary(input_summaries[i], output_summaries[i]);
    }
    return convert_count;
}

std::size_t XYParser_GetAlgorithmDataValueCount()
{
    return XYPARSER_FRAME_ALGORITHM_VALUE_COUNT;
}

int XYParser_ConvertSampleFramesToAlgorithmData(const XYParserFrameSummary* input_summary,
                                                double* output_data)
{
    if (input_summary == nullptr || output_data == nullptr) {
        return 0;
    }
    if (input_summary->channel_count != 64) {
        return 0;
    }

    Convert64ChSummaryToAlgorithmData(*input_summary, output_data);
    return 1;
}

int XYParser_FeedAlgorithmData(XYParserHandle handle,
                               const std::uint8_t* data,
                               std::size_t size,
                               XYParserFrameSummary* out_summaries,
                               int max_summaries)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr || context->channel_count != kAlgorithmChannelCount ||
        data == nullptr || size == 0) {
        return 0;
    }

    if (max_summaries < 0) {
        max_summaries = 0;
    }
    if (max_summaries > 0 && out_summaries == nullptr) {
        return 0;
    }

    context->algorithm_byte_cache.insert(context->algorithm_byte_cache.end(), data, data + size);

    while (context->algorithm_byte_cache.size() >= kAlgorithmSampleByteCount) {
        AlgorithmSample sample{};
        std::array<double, XYPARSER_FRAME_DATA_COLUMN_COUNT> row{};
        std::memcpy(row.data(), context->algorithm_byte_cache.data(), kAlgorithmSampleByteCount);
        for (std::size_t channel_index = 0; channel_index < XYPARSER_MAX_CHANNELS; ++channel_index) {
            sample.channel_values_uv[channel_index] = row[channel_index];
        }
        sample.trigger_type = static_cast<std::uint8_t>(row[XYPARSER_FRAME_DATA_TRIGGER_TYPE_INDEX]);
        sample.trigger_index = static_cast<std::uint8_t>(row[XYPARSER_FRAME_DATA_TRIGGER_INDEX_INDEX]);
        context->welch_processor.PushSample(sample.channel_values_uv);
        context->algorithm_sample_cache.push_back(sample);
        context->algorithm_byte_cache.erase(
            context->algorithm_byte_cache.begin(),
            context->algorithm_byte_cache.begin() + static_cast<std::ptrdiff_t>(kAlgorithmSampleByteCount));
    }

    int output_count = 0;
    while (context->algorithm_sample_cache.size() >= static_cast<std::size_t>(XYPARSER_SAMPLES_PER_FRAME)) {
        const std::vector<AlgorithmSample> frame_samples(
            context->algorithm_sample_cache.begin(),
            context->algorithm_sample_cache.begin() + XYPARSER_SAMPLES_PER_FRAME);
        XYParserFrameSummary summary{};
        FillSummaryFromAlgorithmSamples(frame_samples, context->next_algorithm_frame_index++, summary);
        if (output_count < max_summaries) {
            out_summaries[output_count] = summary;
            ++output_count;
        }
        context->algorithm_sample_cache.erase(
            context->algorithm_sample_cache.begin(),
            context->algorithm_sample_cache.begin() + XYPARSER_SAMPLES_PER_FRAME);
    }

    return output_count;
}

void XYParser_ResetAlgorithmDataCache(XYParserHandle handle)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr || context->channel_count != kAlgorithmChannelCount) {
        return;
    }

    context->algorithm_byte_cache.clear();
    context->algorithm_sample_cache.clear();
    context->next_algorithm_frame_index = 1;
}

int XYParser_FlushAlgorithmData(XYParserHandle handle,
                                XYParserFrameSummary* out_summary)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr || context->channel_count != kAlgorithmChannelCount ||
        out_summary == nullptr || context->algorithm_sample_cache.empty()) {
        return 0;
    }

    FillSummaryFromAlgorithmSamples(
        context->algorithm_sample_cache,
        context->next_algorithm_frame_index++,
        *out_summary);
    context->algorithm_sample_cache.clear();
    return 1;
}

int XYParser_GetLeadMapCount(std::uint8_t channel_count)
{
    return GetLeadMapCount(channel_count);
}

int XYParser_GetLeadMap(std::uint8_t channel_count,
                        XYParserLeadChannelNumber* out_leads,
                        int max_leads)
{
    const int required_count = GetLeadMapCount(channel_count);
    if (required_count <= 0 || out_leads == nullptr || max_leads < required_count) {
        return 0;
    }

    if (channel_count == 8) {
        std::copy(k8ChLeadMap.begin(), k8ChLeadMap.end(), out_leads);
        return required_count;
    }

    std::copy(k64ChLeadMap.begin(), k64ChLeadMap.end(), out_leads);
    return required_count;
}

const char* XYParser_GetLeadName(XYParserLeadChannelNumber lead)
{
    switch (lead) {
    case LeadChannel_FP1: return "FP1";
    case LeadChannel_FP2: return "FP2";
    case LeadChannel_PO6: return "PO6";
    case LeadChannel_POZ: return "POZ";
    case LeadChannel_F3: return "F3";
    case LeadChannel_F4: return "F4";
    case LeadChannel_FPZ: return "FPZ";
    case LeadChannel_AF4: return "AF4";
    case LeadChannel_FC3: return "FC3";
    case LeadChannel_PO8: return "PO8";
    case LeadChannel_CP2: return "CP2";
    case LeadChannel_CP1: return "CP1";
    case LeadChannel_FCZ: return "FCZ";
    case LeadChannel_PO5: return "PO5";
    case LeadChannel_FC2: return "FC2";
    case LeadChannel_FC1: return "FC1";
    case LeadChannel_C3: return "C3";
    case LeadChannel_C4: return "C4";
    case LeadChannel_FC4: return "FC4";
    case LeadChannel_CP4: return "CP4";
    case LeadChannel_P3: return "P3";
    case LeadChannel_P4: return "P4";
    case LeadChannel_F5: return "F5";
    case LeadChannel_C5: return "C5";
    case LeadChannel_F6: return "F6";
    case LeadChannel_PO4: return "PO4";
    case LeadChannel_CP6: return "CP6";
    case LeadChannel_CP5: return "CP5";
    case LeadChannel_PO3: return "PO3";
    case LeadChannel_CP3: return "CP3";
    case LeadChannel_FC6: return "FC6";
    case LeadChannel_FC5: return "FC5";
    case LeadChannel_CB1: return "CB1";
    case LeadChannel_CB2: return "CB2";
    case LeadChannel_P5: return "P5";
    case LeadChannel_AF7: return "AF7";
    case LeadChannel_A1: return "A1";
    case LeadChannel_T7: return "T7";
    case LeadChannel_FT7: return "FT7";
    case LeadChannel_TP7: return "TP7";
    case LeadChannel_FT8: return "FT8";
    case LeadChannel_AF8: return "AF8";
    case LeadChannel_F8: return "F8";
    case LeadChannel_F7: return "F7";
    case LeadChannel_P6: return "P6";
    case LeadChannel_C6: return "C6";
    case LeadChannel_O2: return "O2";
    case LeadChannel_O1: return "O1";
    case LeadChannel_T8: return "T8";
    case LeadChannel_P7: return "P7";
    case LeadChannel_CZ: return "CZ";
    case LeadChannel_PZ: return "PZ";
    case LeadChannel_P8: return "P8";
    case LeadChannel_FZ: return "FZ";
    case LeadChannel_OZ: return "OZ";
    case LeadChannel_PO7: return "PO7";
    case LeadChannel_TP8: return "TP8";
    case LeadChannel_AF3: return "AF3";
    case LeadChannel_C2: return "C2";
    case LeadChannel_C1: return "C1";
    case LeadChannel_P2: return "P2";
    case LeadChannel_P1: return "P1";
    case LeadChannel_F2: return "F2";
    case LeadChannel_F1: return "F1";
    case LeadChannel_GND: return "GND";
    default:
        return "";
    }
}

const char* XYParser_GetWelchBandName(int band_index)
{
    if (band_index < 0 || band_index >= static_cast<int>(kWelchBandInfos.size())) {
        return "";
    }
    return kWelchBandInfos[static_cast<std::size_t>(band_index)].name;
}

int XYParser_GetWelchBandRange(int band_index, double* out_low_hz, double* out_high_hz)
{
    if (band_index < 0 || band_index >= static_cast<int>(kWelchBandInfos.size()) ||
        out_low_hz == nullptr || out_high_hz == nullptr) {
        return 0;
    }

    const WelchBandInfo& band_info = kWelchBandInfos[static_cast<std::size_t>(band_index)];
    *out_low_hz = band_info.low_hz;
    *out_high_hz = band_info.high_hz;
    return 1;
}

int XYParser_ReadImpedance(XYParserHandle handle,
                           XYParserImpedanceSummary* out_summaries,
                           int max_summaries)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return 0;
    }

    return context->impedance_processor.Read(out_summaries, max_summaries);
}

int XYParser_ReadWelch(XYParserHandle handle,
                       XYParserWelchSummary* out_summaries,
                       int max_summaries)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return 0;
    }

    return context->welch_processor.Read(out_summaries, max_summaries);
}

std::size_t XYParser_Get8ChImpedanceCommandSize()
{
    return k8ChImpedanceCommandSize;
}

int XYParser_Serialize8ChImpedanceCommand(int open,
                                          std::uint8_t* out_command,
                                          std::size_t command_size)
{
    if (out_command == nullptr || command_size < k8ChImpedanceCommandSize) {
        return 0;
    }

    const auto command = Build8ChImpedanceCommand(open != 0);
    std::copy(command.begin(), command.end(), out_command);
    return static_cast<int>(command.size());
}

const char* XYParser_GetLastError(XYParserHandle handle)
{
    ParserContext* context = static_cast<ParserContext*>(handle);
    if (context == nullptr) {
        return "invalid parser handle";
    }
    return context->last_error.c_str();
}

}
