/**
 * Copyright (C) 2011 Anders Sundman <anders@4zm.org>
 *
 * This file is part of mfterm.
 *
 * mfterm 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.
 *
 * mfterm 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 mfterm.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mifare.h"
#include "util.h"
#include "tag.h"

mf_tag_t current_tag;
mf_tag_t current_auth;

void strip_non_auth_data(mf_tag_t* tag);
int load_mfd(const char* fn, mf_tag_t* tag);
int save_mfd(const char* fn, const mf_tag_t* tag);

int load_mfd(const char* fn, mf_tag_t* tag) {
  FILE* mfd_file = fopen(fn, "rb");

  if (mfd_file == NULL) {
    printf("Could not open file: %s\n", fn);
    return 1;
  }

  if (fread(tag, 1, sizeof(mf_tag_t), mfd_file) != sizeof(mf_tag_t)) {
    printf("Could not read file: %s\n", fn);
    fclose(mfd_file);
    return 1;
  }

  fclose(mfd_file);
  return 0;
}

int save_mfd(const char* fn, const mf_tag_t* tag) {
  FILE* mfd_file = fopen(fn, "w");

  if (mfd_file == NULL) {
    printf("Could not open file for writing: %s\n", fn);
    return 1;
  }

  if (fwrite(tag, 1, sizeof(mf_tag_t), mfd_file) != sizeof(mf_tag_t)) {
    printf("Could not write file: %s\n", fn);
    fclose(mfd_file);
    return 1;
  }

  fclose(mfd_file);
  return 0;
}

int load_tag(const char* fn) {
  return load_mfd(fn, &current_tag);
}

int save_tag(const char* fn) {
  return save_mfd(fn, &current_tag);
}

int load_auth(const char* fn) {
  if (load_mfd(fn, &current_auth))
    return 1;

  strip_non_auth_data(&current_auth);
  return 0;
}

int save_auth(const char* fn) {
  return save_mfd(fn, &current_auth);
}


int import_auth() {
  memcpy(&current_auth, &current_tag, sizeof(mf_tag_t));
  strip_non_auth_data(&current_auth);
  return 0;
}

void print_tag(mf_size_t size) {
  if (size == MF_1K)
    print_tag_block_range(0, MF_1K / sizeof(mf_block_t) - 1);
  else if (size == MF_4K)
    print_tag_block_range(0, MF_4K / sizeof(mf_block_t) - 1);
  else {
    printf("Unsupported tag size.\n");
  }
  return;
}

void print_tag_head() {
  print_tag_block_range(0, 3);
  return;
}

void print_tag_byte_bits(size_t byte, size_t first_bit, size_t last_bit) {

  // The byte to show parts of
  uint8_t data = current_tag.amb[byte / 16].mbd.abtData[byte % 16];

  printf("[");

  for (size_t i = 0; i < 8; ++i) {
    // Separate nibbles
    if (i == 4)
      printf(" ");

    // Outside mask
    if (i < first_bit || i > last_bit) {
      printf("-");
      continue;
    }

    // Inside mask
    if ((1<<i) & data)
      printf("1");
    else
      printf("0");
  }

  printf("]");
}

void print_tag_bytes(size_t first_byte, size_t last_byte) {

  // Write the data one block at a time
  while (first_byte <= last_byte) {

    size_t byte_len = last_byte - first_byte;

    // Fill up start with spaces
    size_t block_offset = first_byte % 16;
    for (size_t i = 0; i < block_offset; ++i)
      printf("-- ");

    // Print the data
    uint8_t* block_data = current_tag.amb[first_byte / 16].mbd.abtData;
    size_t block_last = block_offset + byte_len;
    if (block_last > 15)
      block_last = 15;
    print_hex_array_sep(block_data  + block_offset,
                        block_last - block_offset + 1, " ");

    // Fill up end with spaces
    for (size_t i = block_last; i < 15; ++i)
      printf("-- ");

    // Finish of with a nl
    printf("\n");

    first_byte += block_last - block_offset + 1;
  }
}

void print_tag_data_range(size_t byte_offset, size_t bit_offset,
                          size_t byte_len, size_t bit_len) {

  printf("Offset: [%zu, %zu] Length: [%zu, %zu]\n",
         byte_offset, bit_offset, byte_len, bit_len);

  // Print partial first byte
  if (bit_offset) {
    size_t total_bits = byte_len * 8 + bit_len;
    size_t last_bit = bit_offset + total_bits - 1;
    if (last_bit > 7)
      last_bit = 7;

    print_tag_byte_bits(byte_offset, bit_offset, last_bit);
    printf("\n");

    total_bits -= last_bit - bit_offset + 1;

    // Update data to be printed
    byte_offset++;
    bit_offset = 0;
    byte_len = total_bits / 8;
    bit_len = total_bits % 8;
  }

  // Print bytes
  if (byte_len) {
    print_tag_bytes(byte_offset, byte_offset + byte_len - 1);

    // Update data to be printed
    byte_offset += byte_len;
    byte_len = 0;
  }

  // Print trailing bits
  if (bit_len) {
    print_tag_byte_bits(byte_offset, 0, bit_len);
    printf("\n");
  }
}


void print_tag_block_range(size_t first, size_t last) {

  // Print header
  printf("xS  xB  00                   07 08                   0f        ASCII       \n");
  printf("---------------------------------------------------------------------------\n");

  // Iterate over all blocks
  for (size_t block = first; block <= last; ++block) {

    // Sector number
    printf("%02zx  ",
           block < 0x10*4 ? block / 4 : 0x10 + (block - 0x10*4) / 0x10);

    // Block number
    printf("%02zx  ", block);

    // then print the block data
    print_hex_array_sep(current_tag.amb[block].mbd.abtData,
                        sizeof(mf_block_t), " ");

    // finally, an ascii rendering
    printf(" [");
    print_ascii_rendering(current_tag.amb[block].mbd.abtData,
                         sizeof(mf_block_t), '.');
    printf("]");

    // EOL
    printf("\n");

    // Indicate sector bondaries with extra nl
    if (block < last && block < 16*4 && (block + 1) % 4 == 0)
      printf("\n");
    else if (block < last && block > 16*4 && (block + 1) % 16 == 0)
      printf("\n");
  }
}

void print_keys(const mf_tag_t* tag, mf_size_t size) {
  printf("xS  xB  KeyA          KeyB\n");
  printf("----------------------------------\n");
  for (int block = 3; block < 0x10 * 4; block += 4) {
    printf("%02x  %02x  ", block / 4, block);
    print_hex_array(tag->amb[block].mbt.abtKeyA, 6);
    printf("  ");
    print_hex_array(tag->amb[block].mbt.abtKeyB, 6);
    printf("\n");
  }

  if (size == MF_1K)
    return;

  printf("\n");

  for (int block = 0xf; block < 0x0c * 0x10; block += 0x10) {
    printf("%02x  %02x  ", 0x10 + block/0x10, 0x10*4 + block);
    print_hex_array(tag->amb[0x10*4 + block].mbt.abtKeyA, 6);
    printf("  ");
    print_hex_array(tag->amb[0x10*4 + block].mbt.abtKeyB, 6);
    printf("\n");
  }
}

void print_ac(const mf_tag_t* tag) {

  static const char* ac_data_str[8] = {
    /* 0 0 0 */ "   A|B A|B A|B A|B   .   .   .   .   .   .",
    /* 0 0 1 */ "   A|B  x   x  A|B   .   .   .   .   .   .",
    /* 0 1 0 */ "   A|B  x   x   x    .   .   .   .   .   .",
    /* 0 1 1 */ "    B   B   x   x    .   .   .   .   .   .",
    /* 1 0 0 */ "   A|B  B   x   x    .   .   .   .   .   .",
    /* 1 0 1 */ "    B   x   x   x    .   .   .   .   .   .",
    /* 1 1 0 */ "   A|B  B   B  A|B   .   .   .   .   .   .",
    /* 1 1 1 */ "    x   x   x   x    .   .   .   .   .   .",
  };

  static const char* ac_trailer_str[8] = {
    /* 0 0 0 */ "    .   .   .   .    x   A   A   x   A   A",
    /* 0 0 1 */ "    .   .   .   .    x   A   A   A   A   A",
    /* 0 1 0 */ "    .   .   .   .    x   x   A   x   A   x",
    /* 0 1 1 */ "    .   .   .   .    x   B  A|B  B   x   B",
    /* 1 0 0 */ "    .   .   .   .    x   B  A|B  x   x   B",
    /* 1 0 1 */ "    .   .   .   .    x   x  A|B  B   x   x",
    /* 1 1 0 */ "    .   .   .   .    x   x  A|B  x   x   x",
    /* 1 1 1 */ "    .   .   .   .    x   x  A|B  x   x   x",
  };

  // Print header
  printf("xS  xB  Raw       C1 C2 C3    R   W   I   D   AR  AW  ACR ACW BR  BW\n");
  printf("--------------------------------------------------------------------\n");

  // Iterate over all blocks (in 1k sectors)
  for (size_t block = 0; block < 0x10 * 4; ++block) {

    // Sector number
    printf("%02zx  ",
           block < 0x10*4 ? block / 4 : 0x10 + (block - 0x10*4) / 0x10);

    // Block number
    printf("%02zx  ", block);

    const uint8_t* ac = tag->amb[block_to_trailer(block)].mbt.abtAccessBits;

    // Print raw bytes
    print_hex_array(ac, 4);

    // Print the C1, C2, C3 bits
    int c1 = (ac[1] & 1<<(4 + (block % 4))) > 0;
    int c2 = (ac[2] & 1<<(0 + (block % 4))) > 0;
    int c3 = (ac[2] & 1<<(4 + (block % 4))) > 0;
    printf("   %d  %d  %d", c1, c2, c3);

    // Print enterpretation
    int c123 = (c1<<2) | (c2<<1) | c3;
    if (block % 4 < 3) {
      // Data block
      printf("%s", ac_data_str[c123]);
    }
    else {
      // Trailer block
      printf("%s", ac_trailer_str[c123]);
    }

    printf("\n");

    // Indicate sector bondaries with extra nl
    if ((block + 1) % 4 == 0)
      printf("\n");
  }
}


const char* sprint_key(const uint8_t* key) {
  static char str_buff[13];

  if (!key)
    return NULL;

  sprintf(str_buff, "%02x%02x%02x%02x%02x%02x",
          (unsigned int)(key[0]),
          (unsigned int)(key[1]),
          (unsigned int)(key[2]),
          (unsigned int)(key[3]),
          (unsigned int)(key[4]),
          (unsigned int)(key[5]));

  return str_buff;
}

// Return a string describing the tag type 1k|4k
const char* sprint_size(mf_size_t size) {
  static const char* str_1k = "1k";
  static const char* str_4k = "4k";

  if (size == MF_1K)
    return str_1k;

  if (size == MF_4K)
    return str_4k;

  return NULL;
}


uint8_t* read_key(uint8_t* key, const char* str) {
  if (!key || !str)
    return NULL;

  static char byte_tok[] = {0, 0, 0};
  char* byte_tok_end;
  for (int i = 0; i < 6; ++i) {
    byte_tok[0] = str[i*2];
    byte_tok[1] = str[i*2+1];
    key[i] = (uint8_t)strtol(byte_tok, &byte_tok_end, 16);
    if (*byte_tok_end != '\0') {
      return NULL;
    }
  }

  return key;
}

void clear_tag(mf_tag_t* tag) {
  memset((void*)tag, 0x00, MF_4K);
}

void strip_non_auth_data(mf_tag_t* tag) {
  static const size_t bs = sizeof(mf_block_t);

  // Clear 1k sector data 16 á 4 - only keep sector trailer
  for (size_t i = 0; i < 0x10; ++i)
    memset(((void*)tag) + i * 4 * bs, 0x00, 3 * bs);

  // Clear 2-4k sector data 12 á 16 - only keep sector trailer
  for (size_t i = 0; i < 0x0c; ++i)
    memset(((void*)tag) + 0x10 * 4 * bs + i * 0x10 * bs, 0x00, 0x0f * bs);
}


size_t block_count(mf_size_t size) {
  return size / 0x10;
}

size_t sector_count(mf_size_t size) {
  return size == MF_1K ? 0x10 : 0x1c;
}

int is_trailer_block(size_t block) {
  return (block + 1) % (block < 0x80 ? 4 : 0x10) == 0;
}

size_t block_to_sector(size_t block) {
  if (block < 0x10*4)
    return block / 4;

  return 0x10 + (block - 0x10*4) / 0x10;
}

size_t block_to_header(size_t block) {
  if (block < 0x10*4)
    return block + (block % 4);

  return block + (block % 0x10);
}

// Return the trailer block for the specified block
size_t block_to_trailer(size_t block)
{
  if (block < 0x10*4)
    return block + (3 - (block % 4));

  return block + (0xf - (block % 0x10));
}

// Return the trailer block for the specified sector
size_t sector_to_trailer(size_t sector) {
  if (sector < 0x10)
    return sector * 4 + 3;
  else
    return 0x10 * 4 + (sector - 0x10) * 0x10 + 0xf;
}

// Return the sector size (in blocks) that contains the block
size_t sector_size(size_t block) {
  return block < 0x10*4 ? 4 : 16;
}

// Extract the key for the block parameters sector of the tag and return it
uint8_t* key_from_tag(const mf_tag_t* tag,
                     mf_key_type_t key_type,
                     size_t block) {

  static uint8_t key[6];

  size_t trailer_block = block_to_trailer(block);

  if (key_type == MF_KEY_A)
    memcpy(key, tag->amb[trailer_block].mbt.abtKeyA, 6);
  else
    memcpy(key, tag->amb[trailer_block].mbt.abtKeyB, 6);

  return key;
}

// Write key to the sector of a tag, where the sector is specified by
// the block.
void key_to_tag(mf_tag_t* tag, const uint8_t* key,
                mf_key_type_t key_type, size_t block) {

  size_t trailer_block = block_to_trailer(block);

  if (key_type == MF_KEY_A)
    memcpy(tag->amb[trailer_block].mbt.abtKeyA, key, 6);
  else
    memcpy(tag->amb[trailer_block].mbt.abtKeyB, key, 6);
}

/**
 * Return block index of the first block in every sector in turn on
 * repeated calls. Initialize the iterator by calling with state
 * 0. Subsequent calls should use the tag size as state. The iterator
 * returns -1 as an end marker.
 */
int sector_header_iterator(int state) {
  static int block;

  if (state == 0)
    return block = 0;

  if (block + 4 < 0x10*4)
    return block += 4;

  if (state == MF_1K) // End marker for 1k state
    return -1;

  if (block + 0x10 < 0x100)
    return block += 0x10;

  return -1; // End marker for 4k state
}
