/*****************************************************************************\
*                                                                             *
*  Name   : checker                                                           *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*  Purpose:                                                                   *
*                                                                             *
*    Generates checksums for a set of files.                                  *
*                                                                             *
*******************************************************************************
* Copyright (c) 1990-$now By Author.  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 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

#include <basis/function.h>
#include <basis/istring.h>
#include <basis/utility.h>
#include <data_struct/static_memory_gremlin.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

const int buffer_size = 4096;

HOOPLE_STARTUP_CODE;

//#define DEBUG_CHECKER
  // uncomment for noisy version.

void print_instructions_and_exit(char *program_name)
{
  printf("\n\
Usage:\n\t%s [-t] filename [filename]\n\n\
This program generates a checksum for each file that is entered on the\n\
command line.  The checksum is (hopefully) an architecture independent\n\
number that is a very compressed representation of the file gestalt.\n\
If one compares two copies of a file, then the checksums should be identical.\n\
This is a useful test of whether a file copy or a program download is\n\
successful in making an identical version of the file.  In particular, if the\n\
file is made slightly bigger or smaller, or if an item in the file is changed,\n\
then the checksums of the two versions should be different numbers.\n\n\
The -b flag is used if the files are to be compared as binary files, and this\n\
is also the default.  The -t flag is used if the files are to be compared as\n\
text files.\n",
  program_name);
  exit(1);
}

#define HIGHEST_CHECK 32714

// do_checksum: takes the specified file name and generates a checksum for it.
// if the file is inaccessible or, at any point, reading it returns an
// error message, then a negative value is returned.
int do_checksum(char *file_name, int open_as_a_text_file)
{
  char file_open_mode[10];
  if (open_as_a_text_file) strcpy(file_open_mode, "rt");
  else strcpy(file_open_mode, "rb");
  FILE *opened_file = fopen(file_name, file_open_mode);
#ifdef DEBUG_CHECKER
  LOG(istring("opened ") + file_name);
#endif
  if (!opened_file) return common::NOT_FOUND;
  int characters_read = 0;
  int current_checksum_value = 0;
  char buffer_chunk[buffer_size];
  while (!feof(opened_file)) {
    characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size,
        opened_file));
    // if result is 0 or negative, stop messing with the file.
#ifdef DEBUG_CHECKER
    LOG(isprintf("char read = %d", characters_read));
#endif
    if (characters_read <= 0) {
      if (characters_read < 0) current_checksum_value = -1;
      else if (current_checksum_value == 0) current_checksum_value = -1;
      break;
    }
    current_checksum_value = (current_checksum_value
            + utility::bizarre_checksum((byte *)buffer_chunk, characters_read))
        % HIGHEST_CHECK;
#ifdef DEBUG_CHECKER
    LOG(isprintf("current checksum=%d", current_checksum_value));
#endif
  }
  fclose(opened_file);
  return int(current_checksum_value);
}

// do_fletcher_checksum: takes the specified file name and generates a fletcher
// checksum for it.  if the file is inaccessible or, at any point,
// reading it returns an error message, then a negative value is returned.
int do_fletcher_checksum(char *file_name, int open_as_a_text_file)
{
  char file_open_mode[10];
  if (open_as_a_text_file) strcpy(file_open_mode, "rt");
  else strcpy(file_open_mode, "rb");
  FILE *opened_file = fopen(file_name, file_open_mode);
#ifdef DEBUG_CHECKER
  LOG(istring("opened ") + file_name);
#endif
  if (!opened_file) return common::NOT_FOUND;
  int characters_read = 0;
  int current_checksum_value = 0;
  char buffer_chunk[buffer_size];
  while (!feof(opened_file)) {
    characters_read = int(fread(buffer_chunk, sizeof(char), buffer_size,
        opened_file));
    // if result is 0 or negative, stop messing with the file.
#ifdef DEBUG_CHECKER
    LOG(isprintf("char read = %d", characters_read));
#endif
    if (characters_read <= 0) {
      if (characters_read < 0) current_checksum_value = -1;
      else if (current_checksum_value == 0) current_checksum_value = -1;
      break;
    }
    current_checksum_value = utility::rolling_fletcher_checksum
        ((uint16)current_checksum_value, (byte *)buffer_chunk,
        characters_read);
#ifdef DEBUG_CHECKER
    LOG(isprintf("current checksum=%d", current_checksum_value));
#endif
  }
  fclose(opened_file);
  return current_checksum_value;
}

int main(int argc, char *argv[])
{
  char name[200];

  // if the file is to be read as a text file, then this is true.
  int open_file_as_text = false;

  if (argc <= 1) print_instructions_and_exit(argv[0]);
  else {
    int current_parameter = 0;
    if (argv[1][0] == '-') {
      if (argv[1][1] == 't') {
        current_parameter++;
        open_file_as_text = true;
      } else if (argv[1][1] == 'b') {
        current_parameter++;
        open_file_as_text = false;
      } else print_instructions_and_exit(argv[0]);
    }
    bool printed_header = false;
    while (++current_parameter < argc) {
      if (!printed_header) {
        printed_header = true;
        printf("bizarro  fletcher  filename\n");
        printf("=======  ========  ========\n");
      }
      strcpy(name, argv[current_parameter]);
      int checksum_of_file = do_checksum(name, open_file_as_text);
      int fletcher_chksum = do_fletcher_checksum(name, open_file_as_text);
      if (checksum_of_file >= 0) {
        printf(isprintf(" %05d    0x%04x   %s\n", checksum_of_file,
            fletcher_chksum, name).s());
      } else {
        printf(isprintf("%s is inaccessible.\n", name).s());
      }
    }
  }
  return 0;
}

#ifdef __BUILD_STATIC_APPLICATION__
  // static dependencies found by buildor_gen_deps.sh:
  #include <basis/array.cpp>
  #include <basis/byte_array.cpp>
  #include <basis/callstack_tracker.cpp>
  #include <basis/chaos.cpp>
  #include <basis/convert_utf.cpp>
  #include <basis/definitions.cpp>
  #include <basis/earth_time.cpp>
  #include <basis/guards.cpp>
  #include <basis/istring.cpp>
  #include <basis/log_base.cpp>
  #include <basis/memory_checker.cpp>
  #include <basis/mutex.cpp>
  #include <basis/object_base.cpp>
  #include <basis/outcome.cpp>
  #include <basis/packable.cpp>
  #include <basis/portable.cpp>
  #include <basis/sequence.cpp>
  #include <basis/set.cpp>
  #include <basis/utility.cpp>
  #include <basis/version_record.cpp>
  #include <data_struct/amorph.cpp>
  #include <data_struct/bit_vector.cpp>
  #include <data_struct/byte_hasher.cpp>
  #include <data_struct/configurator.cpp>
  #include <data_struct/hash_table.cpp>
  #include <data_struct/pointer_hash.cpp>
  #include <data_struct/stack.cpp>
  #include <data_struct/static_memory_gremlin.cpp>
  #include <data_struct/string_hash.cpp>
  #include <data_struct/string_hasher.cpp>
  #include <data_struct/string_table.cpp>
  #include <data_struct/symbol_table.cpp>
  #include <data_struct/table_configurator.cpp>
  #include <loggers/console_logger.cpp>
  #include <loggers/file_logger.cpp>
  #include <loggers/locked_logger.cpp>
  #include <loggers/null_logger.cpp>
  #include <loggers/program_wide_logger.cpp>
  #include <opsystem/byte_filer.cpp>
  #include <opsystem/command_line.cpp>
  #include <opsystem/critical_events.cpp>
  #include <opsystem/directory.cpp>
  #include <opsystem/filename.cpp>
  #include <opsystem/ini_config.cpp>
  #include <opsystem/ini_parser.cpp>
  #include <opsystem/path_configuration.cpp>
  #include <opsystem/rendezvous.cpp>
  #include <textual/byte_format.cpp>
  #include <textual/parser_bits.cpp>
  #include <textual/string_manipulation.cpp>
  #include <textual/tokenizer.cpp>
#endif // __BUILD_STATIC_APPLICATION__

