/*
 * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
 *
 * This file is part of PortaPack.
 *
 * 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 2, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include "ais_packet.hpp"

#include "crc.hpp"

#include <cstdlib>

namespace ais {

struct PacketLengthRange {
    constexpr PacketLengthRange()
        : min_bytes{0},
          max_bytes{0} {
    }

    constexpr PacketLengthRange(
        const uint16_t min_bits,
        const uint16_t max_bits)
        : min_bytes{static_cast<uint8_t>(min_bits / 8U)},
          max_bytes{static_cast<uint8_t>(max_bits / 8U)} {
        // static_assert((min_bits & 7) == 0, "minimum bits not a multiple of 8");
        // static_assert((max_bits & 7) == 0, "minimum bits not a multiple of 8");
    }

    constexpr bool contains(const size_t bit_count) const {
        return !is_above(bit_count) && !is_below(bit_count);
    }

    constexpr bool is_above(const size_t bit_count) const {
        return (min() > bit_count);
    }

    constexpr bool is_below(const size_t bit_count) const {
        return (max() < bit_count);
    }

    constexpr size_t min() const {
        return min_bytes * 8;
    }

    constexpr size_t max() const {
        return max_bytes * 8;
    }

   private:
    const uint8_t min_bytes;
    const uint8_t max_bytes;
};

static constexpr std::array<PacketLengthRange, 64> packet_length_range{{
    {0, 0},      // 0
    {168, 168},  // 1
    {168, 168},  // 2
    {168, 168},  // 3
    {168, 168},  // 4
    {424, 424},  // 5
    {0, 0},      // 6
    {0, 0},      // 7
    {0, 1008},   // 8
    {0, 0},      // 9
    {0, 0},      // 10
    {0, 0},      // 11
    {0, 0},      // 12
    {0, 0},      // 13
    {0, 0},      // 14
    {0, 0},      // 15
    {0, 0},      // 16
    {0, 0},      // 17
    {168, 168},  // 18
    {0, 0},      // 19
    {72, 160},   // 20
    {272, 360},  // 21
    {168, 168},  // 22
    {160, 160},  // 23
    {160, 168},  // 24
    {0, 168},    // 25
    {0, 0},      // 26
    {0, 0},      // 27
    {0, 0},      // 28
    {0, 0},      // 29
    {0, 0},      // 30
    {0, 0},      // 31
}};

struct PacketLengthValidator {
    constexpr bool operator()(const uint_fast8_t message_id, const size_t length) const {
        return packet_length_range[message_id].contains(length);
    }
};

struct PacketTooLong {
    constexpr bool operator()(const uint_fast8_t message_id, const size_t length) const {
        return packet_length_range[message_id].is_below(length);
    }
};

static constexpr char char_to_ascii(const uint8_t c) {
    return (c ^ 32) + 32;
}

size_t Packet::length() const {
    return packet_.size();
}

bool Packet::is_valid() const {
    return length_valid() && crc_ok();
}

Timestamp Packet::received_at() const {
    return packet_.timestamp();
}

uint32_t Packet::message_id() const {
    return field_.read(0, 6);
}

MMSI Packet::user_id() const {
    return field_.read(8, 30);
}

MMSI Packet::source_id() const {
    return field_.read(8, 30);
}

uint32_t Packet::read(const size_t start_bit, const size_t length) const {
    return field_.read(start_bit, length);
}

std::string Packet::text(
    const size_t start_bit,
    const size_t character_count) const {
    std::string result;
    result.reserve(character_count);

    const size_t character_length = 6;
    const size_t end_bit = start_bit + character_count * character_length;
    for (size_t i = start_bit; i < end_bit; i += character_length) {
        result += char_to_ascii(field_.read(i, character_length));
    }

    return result;
}

DateTime Packet::datetime(const size_t start_bit) const {
    return {
        static_cast<uint16_t>(field_.read(start_bit + 0, 14)),
        static_cast<uint8_t>(field_.read(start_bit + 14, 4)),
        static_cast<uint8_t>(field_.read(start_bit + 18, 5)),
        static_cast<uint8_t>(field_.read(start_bit + 23, 5)),
        static_cast<uint8_t>(field_.read(start_bit + 28, 6)),
        static_cast<uint8_t>(field_.read(start_bit + 34, 6)),
    };
}

Latitude Packet::latitude(const size_t start_bit) const {
    return field_.read(start_bit, 27);
}

Longitude Packet::longitude(const size_t start_bit) const {
    return field_.read(start_bit, 28);
}

bool Packet::crc_ok() const {
    CRCReader field_crc{packet_};
    CRC<16> ais_fcs{0x1021, 0xffff, 0xffff};

    for (size_t i = 0; i < data_length(); i += 8) {
        ais_fcs.process_byte(field_crc.read(i, 8));
    }

    return (ais_fcs.checksum() == (unsigned)field_crc.read(data_length(), fcs_length));
}

size_t Packet::data_and_fcs_length() const {
    // Subtract end flag (8 bits) - one unstuffing bit (occurs during end flag).
    return length() - 7;
}

size_t Packet::data_length() const {
    return data_and_fcs_length() - fcs_length;
}

bool Packet::length_valid() const {
    const size_t extra_bits = data_and_fcs_length() & 7;
    if (extra_bits != 0) {
        return false;
    }

    const PacketLengthValidator packet_length_valid;
    if (!packet_length_valid(message_id(), data_length())) {
        return false;
    }

    return true;
}

} /* namespace ais */