#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>

#include "bmp.h"
#include "common.h"
#include "file_cmp.h"
#include "image.h"
#include "io.h"

#define FOR_HEADER(FOR_FIELD)                                                  \
  FOR_FIELD(uint8_t, bfType[2])                                                \
  FOR_FIELD(uint32_t, bfileSize)                                               \
  FOR_FIELD(uint32_t, bfReserved)                                              \
  FOR_FIELD(uint32_t, bOffBits)                                                \
  FOR_FIELD(uint32_t, biSize)                                                  \
  FOR_FIELD(uint32_t, biWidth)                                                 \
  FOR_FIELD(uint32_t, biHeight)                                                \
  FOR_FIELD(uint16_t, biPlanes)                                                \
  FOR_FIELD(uint16_t, biBitCount)                                              \
  FOR_FIELD(uint32_t, biCompression)                                           \
  FOR_FIELD(uint32_t, biSizeImage)                                             \
  FOR_FIELD(uint32_t, biXPelsPerMeter)                                         \
  FOR_FIELD(uint32_t, biYPelsPerMeter)                                         \
  FOR_FIELD(uint32_t, biClrUsed)                                               \
  FOR_FIELD(uint32_t, biClrImportant)

#define DECLARE_FIELD(t, n) t n;

#pragma pack(push, 1)
struct header {
  FOR_HEADER(DECLARE_FIELD)
};
#pragma pack(pop)

#undef FOR_HEADER
#undef DECLARE_FIELD

static size_t get_padding( size_t width ) {return width % 4;}
static bool header_is_correct(const struct header *header) {

  if (header->bfType[0] != 'B' || header->bfType[1] != 'M')
    return false;
  if (header->biBitCount != 24)
    return false;

  return true;
}

enum bmp_compare_status bmp_cmp(FILE *f1, FILE *f2) {
  struct header h1 = {0}, h2 = {0};

  if (!fread(&h1, sizeof(struct header), 1, f1) ||
      !header_is_correct(&h1))
    return BMP_CMP_FILE_ERROR;

  if (!fread(&h2, sizeof(struct header), 1, f2) ||
      !header_is_correct(&h2))
    return BMP_CMP_INVALID_FORMAT;

  if (h1.biWidth != h2.biWidth || h1.biHeight != h2.biHeight)
    return BMP_CMP_DIMENSIONS_DIFFER;

  const size_t padding = get_padding(h1.biWidth);

  for( size_t i = 0; i < h1.biHeight; i++ ) {
    switch (file_cmp( f1, f2, h1.biWidth * 3 ) ) {
    case CMP_DIFF: return BMP_CMP_DIFF;
    case CMP_ERROR: return BMP_CMP_FILE_ERROR;
    case CMP_EQ:
      if (fseek(f1, (long) padding, SEEK_CUR) != 0 ||
          fseek(f2, (long) padding, SEEK_CUR) != 0)
        return BMP_CMP_FILE_ERROR;
      break;
    default: err("Implementation error"); break;
    }
  }
  return BMP_CMP_EQUALS;
}