/*
 * This file belongs to the Galois project, a C++ library for exploiting parallelism.
 * The code is being released under the terms of the 3-Clause BSD License (a
 * copy is located in LICENSE.txt at the top-level directory).
 *
 * Copyright (C) 2018, The University of Texas at Austin. All rights reserved.
 * UNIVERSITY EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES CONCERNING THIS
 * SOFTWARE AND DOCUMENTATION, INCLUDING ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR ANY PARTICULAR PURPOSE, NON-INFRINGEMENT AND WARRANTIES OF
 * PERFORMANCE, AND ANY WARRANTY THAT MIGHT OTHERWISE ARISE FROM COURSE OF
 * DEALING OR USAGE OF TRADE.  NO WARRANTY IS EITHER EXPRESS OR IMPLIED WITH
 * RESPECT TO THE USE OF THE SOFTWARE OR DOCUMENTATION. Under no circumstances
 * shall University be liable for incidental, special, indirect, direct or
 * consequential damages or loss of profits, interruption of business, or
 * related expenses which may arise from use of Software or Documentation,
 * including but not limited to those resulting from defects in Software and/or
 * Documentation, or loss or inaccuracy of data of any kind.
 */

// //////////////////////////////////////////////////////////
// digest.cpp
// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved.
// see http://create.stephan-brumme.com/disclaimer.html
//

// g++ -O3 digest.cpp crc32.cpp md5.cpp sha1.cpp sha256.cpp keccak.cpp sha3.cpp
// -o digest

#include "crc32.h"
#include "md5.h"
#include "sha1.h"
#include "sha256.h"
#include "keccak.h"
#include "sha3.h"

#include <iostream>
#include <fstream>

int main(int argc, char** argv) {
  // syntax check
  if (argc < 2 || argc > 3) {
    std::cout
        << "./digest filename [--crc|--md5|--sha1|--sha256|--keccak|--sha3]"
        << std::endl;
    return 1;
  }

  // parameters
  std::string filename  = argv[1];
  std::string algorithm = argc == 3 ? argv[2] : "";
  bool computeCrc32     = algorithm.empty() || algorithm == "--crc";
  bool computeMd5       = algorithm.empty() || algorithm == "--md5";
  bool computeSha1      = algorithm.empty() || algorithm == "--sha1";
  bool computeSha2 =
      algorithm.empty() || algorithm == "--sha2" || algorithm == "--sha256";
  bool computeKeccak = algorithm.empty() || algorithm == "--keccak";
  bool computeSha3   = algorithm.empty() || algorithm == "--sha3";

  CRC32 digestCrc32;
  MD5 digestMd5;
  SHA1 digestSha1;
  SHA256 digestSha2;
  Keccak digestKeccak(Keccak::Keccak256);
  SHA3 digestSha3(SHA3 ::Bits256);

  // each cycle processes about 1 MByte (divisible by 144 => improves
  // Keccak/SHA3 performance)
  const size_t BufferSize = 144 * 7 * 1024;
  char* buffer            = new char[BufferSize];

  // select input source: either file or standard-in
  std::ifstream file;
  std::istream* input = NULL;
  // accept std::cin, syntax will be: "./digest - --sha3 < data"
  if (filename == "-") {
    input = &std::cin;
  } else {
    // open file
    file.open(filename.c_str(), std::ios::in | std::ios::binary);
    if (!file) {
      std::cerr << "Can't open '" << filename << "'" << std::endl;
      return 2;
    }

    input = &file;
  }

  // process file
  while (*input) {
    input->read(buffer, BufferSize);
    std::size_t numBytesRead = size_t(input->gcount());

    if (computeCrc32)
      digestCrc32.add(buffer, numBytesRead);
    if (computeMd5)
      digestMd5.add(buffer, numBytesRead);
    if (computeSha1)
      digestSha1.add(buffer, numBytesRead);
    if (computeSha2)
      digestSha2.add(buffer, numBytesRead);
    if (computeKeccak)
      digestKeccak.add(buffer, numBytesRead);
    if (computeSha3)
      digestSha3.add(buffer, numBytesRead);
  }

  // clean up
  file.close();
  delete[] buffer;

  // show results
  if (computeCrc32)
    std::cout << "CRC32:      " << digestCrc32.getHash() << std::endl;
  if (computeMd5)
    std::cout << "MD5:        " << digestMd5.getHash() << std::endl;
  if (computeSha1)
    std::cout << "SHA1:       " << digestSha1.getHash() << std::endl;
  if (computeSha2)
    std::cout << "SHA2/256:   " << digestSha2.getHash() << std::endl;
  if (computeKeccak)
    std::cout << "Keccak/256: " << digestKeccak.getHash() << std::endl;
  if (computeSha3)
    std::cout << "SHA3/256:   " << digestSha3.getHash() << std::endl;

  return 0;
}
