#ifndef MD2_COMMON_H
#define MD2_COMMON_H

#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include "../helper.h"

#ifdef MD2_DETAILED_BENCHMARK
#define md2_process_detailed_benchmark_step_if_defined(step) \
  md2_process_detailed_benchmark_step(step);

#define md2_print_detailed_benchmark_result_if_defined \
  md2_print_detailed_benchmark_result();
#else
#define md2_process_detailed_benchmark_step_if_defined(step)
#define md2_print_detailed_benchmark_result_if_defined
#endif  // MD2_DETAILED_BENCHMARK

#define CHECKSUM_START_MARK \
  md2_process_detailed_benchmark_step_if_defined(CHECKSUM_START)
#define CHECKSUM_END_MARK \
  md2_process_detailed_benchmark_step_if_defined(CHECKSUM_END)
#define FIRST_LOOP_START_MARK \
  md2_process_detailed_benchmark_step_if_defined(FIRST_LOOP_START)
#define FIRST_LOOP_END_MARK \
  md2_process_detailed_benchmark_step_if_defined(FIRST_LOOP_END)
#define SECOND_LOOP_START_MARK \
  md2_process_detailed_benchmark_step_if_defined(SECOND_LOOP_START)
#define SECOND_LOOP_END_MARK \
  md2_process_detailed_benchmark_step_if_defined(SECOND_LOOP_END)
#define END_MARK md2_print_detailed_benchmark_result_if_defined

enum md2_detailed_benchmark_step {
  CHECKSUM_START = 0,
  CHECKSUM_END,
  FIRST_LOOP_START,
  FIRST_LOOP_END,
  SECOND_LOOP_START,
  SECOND_LOOP_END
};

/**
 * @brief Some digits of pi
 *
 */
extern unsigned char MD2_PI_SUBST[256];

/**
 * @brief Print a buffer for debugging
 *
 * @param len length
 * @param buf buffer
 */
void md2_print_buf(size_t len, uint8_t buf[len]);

/**
 * @brief Calculates checksum of buf and appends it to buf
 *
 * @param len Length of data which the checksum should be calculated of
 * @param buf Location of the data. Make sure to reserve 16 bytes more so the
 * chechsum fits!
 */
void md2_checksum(size_t len, uint8_t* buf);

/**
 * @brief Calculates checksum of buf and writes it into a target
 *
 * @param len Length of data which the checksum should be calculated of
 * @param buf Location of the data. Make sure to reserve 16 bytes more so the
 * chechsum fits!
 * @param checksum the output checksum
 */
void md2_checksum_with_target(size_t len, const uint8_t* buf,
                              uint8_t checksum[16]);

/**
 * @brief Process one block of the checksum
 *
 * @param block the block to process
 * @param checksum the output checksum
 * @param l the current l
 */
void md2_process_block_checksum(uint8_t const block[16], uint8_t checksum[16],
                                uint8_t* l);

/**
 * @brief Stores the time if start of a step, calculates the duration if end
 *
 * @param step the step to process
 */
void md2_process_detailed_benchmark_step(enum md2_detailed_benchmark_step step);

/**
 * @brief Print the detailed benchmark result
 *
 */
void md2_print_detailed_benchmark_result();

/**
 * @brief Add padding and allocate extra space for the checksum
 *
 * @param buf buffer of the whole message
 * @param len length of buf
 * @return uint8_t* pointer to the new buffer
 */
uint8_t* md2_add_padding_and_space_for_checksum(const uint8_t* buf,
                                                size_t* len);

/**
 * @brief The second loop of the md2 algorithm
 *
 * @param messageDigestBuf the message digest buffer
 */
void md2_second_loop(uint8_t* messageDigestBuf);

/**
 * @brief The first loop of the md2 algorithm
 *
 * @param buf buffer of the whole message
 * @param messageDigestBuf the message digest buffer
 * @param i the index of the 16-byte message block
 */
void md2_first_loop(const uint8_t* buf, uint8_t* messageDigestBuf, size_t i);

#endif  // MD2_COMMON_H