#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>


struct code_struct
{
   // valid is 1 if a valid code has been stored in this object
   // successfully, and valid is 0 otherwise (if, let's say, we 
   // encountered errors while reading the file that stores the code).
   int valid; 
   int m; // number of bits in original word
   int n; // number of bits in codeword
   int number; // number of original words, equals 2^m.
   int distance; // distance of the code.
   
   // original is a 2D array, with 2^m rows and m columns.
   // original[i] is the i-th original word, and it is actually
   // the string of '0' and '1' characters that represents number i
   // in binary.
   char ** original;
   
   // Codebook is a 2D array, with 2^m rows and n columns.
   // Each row is a string of '0' and '1' characters that is
   // the codeword for original[i];
   char ** codebook; 
};

// computes and returns the number of positions where code1 and code2
// differ. length is the number of characters in each of code1 and code2.
int hammingDistance(char * code1, char * code2, int length);

// For the specified word, this function computes the distance to each 
// string in codes, and identifies the index of the most similar codeword.
// It returns that index.
// number is the number of strings in codes, and length is the 
// length of each string in codes.
// If there were ties (more than one codeword had the smallest distance to
// word), *tie_flag is set to 1, otherwise it is set to 0.
int nearestWord(char ** codes, char * word, int number, int length, int * tie_flag);


int codeDistance(struct code_struct code);


void destroyCode(struct code_struct code)
{
   int i;
   for (i = 0; i < code.number; i++)
   {
      free(code.original[i]);
      free(code.codebook[i]);
   }
   
   free(code.original);
   free(code.codebook);
   code.valid = 0;
}

struct code_struct read_code(char * code_file)
{
   struct code_struct result;
   result.valid = -1;
   
   FILE * handle = fopen(code_file, "rb");
   if (handle == 0)
   {
      printf("failed to open file %s\n", code_file);
      return result;
   }

   int items;
   items = fscanf(handle, "%d %d", &(result.m), &(result.n));
   if (items != 2)
   {
      printf("failed to read m and n\n");
      return result;
   }

   result.number = (int) pow(2, result.m);
   
   result.original = malloc(sizeof(char*) * result.number);
   result.codebook = malloc(sizeof(char*) * result.number);
   
   int i;
   for (i = 0; i < result.number; i++)
   {
      result.original[i] = malloc(sizeof(char) * result.m);
      result.codebook[i] = malloc(sizeof(char) * result.n);
   }   
   char buffer1[1000];
   char buffer2[1000];

   for (i = 0; i < result.number; i++)
   {
      items = fscanf(handle, "%s %s", buffer1, buffer2);
      if (items != 2)
      {
         printf("failed to read entry %d\n", i);
         destroyCode(result);
         return result;
      }
      
      int len = strlen(buffer1);
      if (len != result.m)
      {
         printf("error: original word %d has %d characters%d\n", i, len);
         destroyCode(result);
         return result;
      }
      
      len = strlen(buffer2);
      if (len != result.n)
      {
         printf("error: original word %d has %d characters%d\n", i, len);
         destroyCode(result);
         return result;
      }
      
      memcpy(result.original[i], buffer1, result.m);
      memcpy(result.codebook[i], buffer2, result.n);      
   }
   
   fclose(handle);
   result.distance = codeDistance(result);
   result.valid = 1;
   return result;
}


// input_buffer contains a string whose characters are '0' and '1'
// and that represents an original word.
// This identifies and returns the corresponding codeword.
void convert_to_codeword(struct code_struct code, char * input_buffer, char * output_buffer);

// output_buffer contains a string whose characters
// are '0' and '1' and that represents a codeword in binary.
// This function decodes the codeword, and the stores the original 
// word in output_buffer, as a string as a string of 
// binary ('0' and '1') characters.
// returns:
// 1 if no error was detected.
// 0 if an error was detected and corrected.
// -1 if an error was detected but not corrected.
int convert_to_original_word(struct code_struct code, char * input_buffer, char * output_buffer);


// Reads data from the input file (represented by input_handle)
// in chunks of m_parameter characters, that represent an original word.
// Each chunk is encoded, and the n-character-long
// result is stored at the output file (represented by output_handle).
void encode(struct code_struct code, FILE * input_handle, FILE * output_handle)
{
   char * input_buffer = malloc(sizeof(char)*code.m);
   char * output_buffer = malloc(sizeof(char)*code.n);

   while(1)
   {
      // read the next "word" from the input file
      int items = fread(input_buffer, sizeof(char), code.m, input_handle);
      if (items != code.m)
      {
          break;
      }
      
      // This is where your function is called.
      convert_to_codeword(code, input_buffer, output_buffer);
      
      // Write the "codeword" to the output file.
      fwrite(output_buffer, sizeof(char), code.n, output_handle);
   }
   free(input_buffer);
   free(output_buffer);
}


// Reads data from the input file (represented by input_handle)
// in chunks of n characters, that represent codewords in binary digits.
// Each chunk is decoded, and the m-character-long
// result (containing '0' and '1' characters and representing the original
// code in binary) is stored at the output file (represented by 
// output_handle).
// 
// This function also prints, for each detected error:
// - the position of the word containing the error.
void decode(struct code_struct code, FILE * input_handle, FILE * output_handle)
{
   char * input_buffer = malloc(sizeof(char)*code.n);
   char * output_buffer = malloc(sizeof(char)*code.m);
   int word_counter = 0;
   
   while(1)
   {
      int items = fread(input_buffer, sizeof(char), code.n, input_handle);
      if (items != code.n)
      {
          break;
      }
      
      // This is where your function is called.
      int result = convert_to_original_word(code, input_buffer, output_buffer);
      fwrite(output_buffer, sizeof(char), code.m, output_handle);
      if (result == 0)
      {
         printf("error detected and corrected at word %d\n", word_counter);
      }
      else if (result == -1)
      {
         printf("error detected and NOT corrected at word %d\n", word_counter);
      }
      
      word_counter++;
   }
}


int main(int argc, char ** argv)
{
   if (argc != 5)
   {
      printf("error: four command-line arguments needed:\n");
      printf("       number code_file input_file output_file\n");
      return 1;
   }

//   int number = 1;
//   char * code_file = "code1.txt";
//   char * input_file  = "out1.txt";   
//   char * output_file = "out2.txt";
   
   int number = atoi(argv[1]);
   char * code_file = argv[2];
   char * input_file  = argv[3];   
   char * output_file = argv[4];
  
   struct code_struct code = read_code(code_file);
   if (code.valid == 0)
   {
      printf("failed to load a valid code from: %s\n", code_file);
   }
   
   FILE * input_handle = fopen(input_file, "rb");
   FILE * output_handle = fopen(output_file, "wb");

   if (number == 0) // we must do encoding
   {
      // do the encoding
      encode(code, input_handle, output_handle);
      
      // close the files.
      fclose(input_handle);
      fclose(output_handle);
   }
   else if (number == 1)
   {
      // do the decoding and correction.
      decode(code, input_handle, output_handle);
      
      // close the files.
      fclose(input_handle);
      fclose(output_handle);
   }
   
   destroyCode(code);
   return 0;
}   

