// Example solution to Assignment 4.
// CS 509 - Advanced Programming II, Fall 2002

#include <algorithm>
#include <cstdio>
#include <iostream>
#include <string>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
#include <strings.h>
#include <cstdlib>
#include <iterator>
#include "min-3sort.h"


// A set of input vectors.

   struct input_vectors {
     ivec v0, v1, v2;
     };


// The 3-sort procedure signature.

   typedef answer (* min3sort_fun)(ivec, ivec, ivec);


// Don't print too many errors.

   const unsigned err_max = 4;
   static unsigned err_cnt = 0;
 

// Deja vu, c++ style.

   static bool check_times(time_t, time_t);
   static bool run_test(void);
   static void print_time(time_t);
   static answer timed_run(min3sort_fun, const input_vectors &, time_t &);
   static void make_rand_ivec(ivec &);
   static answer min3sort(ivec, ivec, ivec);
   static bool small_answer_check(const input_vectors &);
   std::ostream & operator << (std::ostream &, const answer &);
   std::ostream & operator << (std::ostream &, const ivec &);
   static void print_emsg(const std::string &, const answer &, const answer &);
   static void fill_and_go(
     const input_vectors &, unsigned, unsigned, unsigned, unsigned);


static bool
check_answer(const answer & a, const answer & c) {

  // Compare the computed sort results a to the check sort results c, printing
  // an error message to std-out if anything's wrong.

  if (a.moves > c.moves) {
    print_emsg(
      "calculated move count larger than the check move count", a, c);
    return false;
    }

  if ((a.v1 == a.v2) or (a.v1 == a.v3) or (a.v3 == a.v2)) {
    print_emsg("modulus repeated in computed answer", a, c);
    return false;
    }

  if (((a.v1 != 1) and (a.v1 != 2) and (a.v1 != 0)) or
      ((a.v2 != 1) and (a.v2 != 2) and (a.v2 != 0)) or
      ((a.v3 != 1) and (a.v3 != 2) and (a.v3 != 0))) {
    print_emsg("invalid modulus value in computer answer", a, c);
    return false;
    }

  return true;
  }


static void
check_sort(answer & a, unsigned moduli[], unsigned moduli_cnts[][3]) {

  // Determine the number of moves in a sort in which all residue moduli[0]
  // elements are moved to vector 1, all residue moduli[1] elements are moved
  // to vector 2, and all residue 2 elements are moved to vector 3.  If the
  // sort is better than the results given in a, update a with the new, better
  // results.

  const unsigned moves = 
    moduli_cnts[1][moduli[0]] + moduli_cnts[2][moduli[0]] +
    moduli_cnts[0][moduli[1]] + moduli_cnts[2][moduli[1]] +
    moduli_cnts[0][moduli[2]] + moduli_cnts[1][moduli[2]];

  if (moves < a.moves) {
    a.moves = moves;
    a.v1 = moduli[0];
    a.v2 = moduli[1];
    a.v3 = moduli[2];
    }
  }


static bool
check_times(time_t answert, time_t checkt) { 

  // Compare an answer time to a check time and print a message if the answer
  // time's too slow compared to the check time.  Return true iff the time's
  // are good.

  if (answert > 2*checkt) {
    fprintf(stderr, "\n!!! Computed min-3sort time is");
    print_time(answert);
    fprintf(stderr, " vs. check min-3sort time ");
    print_time(checkt);
    fprintf(stderr, " (x%3.1f).\n",
	    static_cast<double>(answert)/static_cast<double>(checkt));

    return false;
    }

  return true;
  }


static bool
exhaustive_check(const input_vectors & ivecs, unsigned r) {

  // Generate exhaustive 3-sorting tests by adding residue r elements to the
  // given input vectors, unless r is 3, in which case the vectors are full and
  // the sorting should begin.

  if (r == 3)
    return small_answer_check(ivecs);

  unsigned counts[] = { 1, 2, 3 };

  for (unsigned j = 0; j < 4; j++)
    fill_and_go(ivecs, j, j, j, r);

  do {
    fill_and_go(ivecs, counts[0], counts[1], counts[2], r);
    fill_and_go(ivecs, counts[0], counts[1], counts[1], r);
    fill_and_go(ivecs, counts[1], counts[0], counts[1], r);
    fill_and_go(ivecs, counts[1], counts[1], counts[0], r);
    }
  while (std::next_permutation(counts, counts + 3) && (err_cnt < err_max));
  }


static void 
cnt(const ivec & iv, unsigned cnts[3]) {

  // Count the number of elements of residues 0, 1, and 2 in the vector iv,
  // soring the count for residue i in cnts[i].

  cnts[0] = cnts[1] = cnts[2] = 0;
  for (ivec::const_iterator i = iv.begin(); i != iv.end(); i++)
    cnts[*i % 3]++;
  }


static void 
exhaustive_check(void) {

  // Conduct an exhaustive test of the 3-sorting procedure.

  exhaustive_check(input_vectors(), 0);
  }


static void
fill_and_go(const input_vectors & ivecs,
	    unsigned c0, unsigned c1, unsigned c2, 
	    unsigned r) {

  // Fill the input vectors ivecs with numbers having residue r, putting c0
  // numbers in vector v0 and so on.  Then add the next residue.

  if (err_cnt < err_max) {
    const unsigned values[] = { r, r, r };
    input_vectors new_vecs(ivecs);

    new_vecs.v0.insert(new_vecs.v0.end(), values, values + c0);
    new_vecs.v1.insert(new_vecs.v1.end(), values, values + c1);
    new_vecs.v2.insert(new_vecs.v2.end(), values, values + c2);

    exhaustive_check(new_vecs, r + 1);
    }
  }


static void
make_input_vectors(input_vectors & ivecs) {

  // Make a set of random input vectors and store them in ivecs.

  make_rand_ivec(ivecs.v0);
  make_rand_ivec(ivecs.v1);
  make_rand_ivec(ivecs.v2);
  }


static void
make_rand_ivec(ivec & iv) {

  // Make a random input vector and store it in iv.

  unsigned s = random() % 1000000;
  iv.reserve(s);
  while (s-- > 0)
    iv.push_back(random());
  }


int
main(int argc, char * argv[]) {
  
  bool telltime = false;
  unsigned seed = getpid();

  int c;
  extern char *optarg;
  extern int optind;

  while ((c = getopt(argc, argv, "ts:")) != EOF)
     switch (c) {
     case 't':
       telltime = true;
       break;

     case 's':
       seed = atoi(optarg);
       break;

     case '?':
       std::cerr << "Command format is \"" << argv[0] << " [-t] [-sn]\".\n";
       return EXIT_FAILURE;
     }

  if (optind != argc) {
    std::cerr << "Command format is \"" << argv[0] << " [-t] [-sn]\".\n";
    return EXIT_FAILURE;
    }

  exhaustive_check();

  srandom(seed);

  err_cnt = 0;
  for (unsigned i = 0; (i < 20) and (err_cnt < err_max); i++)
    if (!run_test())
      err_cnt++;
  }


static answer
min3sort(ivec iv1, ivec iv2, ivec iv3) {

  // 3-sort the given input vectors using a minimal number of moves.

  answer a;
  unsigned moduli_cnts[3][3];

  cnt(iv1, moduli_cnts[0]);
  cnt(iv2, moduli_cnts[1]);
  cnt(iv3, moduli_cnts[2]);

  a.moves = iv1.size() + iv2.size() + iv3.size();

  unsigned moduli[] = { 0, 1, 2 };

  do check_sort(a, moduli, moduli_cnts);
  while (std::next_permutation(moduli, moduli + 3));

  return a;
  }


std::ostream & 
operator << (std::ostream & os, const answer & a) {

  // Print the answer a to the output stream os, returning os.

  return os << "v1: " << a.v1 << ", v2: " << a.v2 << ", v3: " << a.v3
	    << ", moves: " << a.moves;
  }


std::ostream & 
operator << (std::ostream & os, const ivec & iv) {

  // Print the int vector iv to the output stream os, returning os.

  std::copy(iv.begin(), iv.end(), std::ostream_iterator<unsigned>(os, " "));

  return os;
  }


static void
print_emsg(const std::string & emsg, const answer & a, const answer & c) {

  // Print to std-err the error message given in emsg, including the contents
  // of the computed answer a and the check answer a.

  std::cerr << "\n!!! " << emsg << ".\n";
  std::cerr << "!!! calculated answer " << a << "\n";
  std::cerr << "!!! check answer      " << c << "\n";
  }


static void 
print_time(time_t t) {

  // Convert the micosecond time interval t into a conveniently small number
  // and print it to std-err.

  if (t < 1000)
    fprintf(stderr, "%5u usec", static_cast<unsigned>(t));
  else if (t < 100000)
    fprintf(stderr, "%5.1f msec", static_cast<double>(t)/1000.0);
  else
    fprintf(stderr, "%5.1f  sec", static_cast<double>(t)/1000000.0);
  }


static bool
run_test(void) {

  // Generate a triplit of random input vectors, feed them to the two sorting
  // procedures, and compare the results.

  time_t answert, check_answert;
  input_vectors vecs;

  make_input_vectors(vecs);

  const answer 
    ans = timed_run(min_3sort, vecs, answert),
    check_ans = timed_run(min3sort, vecs, check_answert);

  return check_answer(ans, check_ans) and check_times(answert, check_answert);
  }


static bool
small_answer_check(const input_vectors & ivecs) {

  // Given the set of small (less than ten element) input vectors, run the
  // sorting procedures and compare the results, printing a detailed error
  // message if anything's out of whack.  Return true or false based on the
  // test results.  Also keep track of the number of errors in the global
  // err_cnt to stop testing if there's too many errors.

  const ivec 
    & v1 = ivecs.v0,
    & v2 = ivecs.v1,
    & v3 = ivecs.v2;
  
  if (check_answer(min_3sort(v1, v2, v3), min3sort(v1, v2, v3))) 
    return true;

  std::cerr << "Input vectors were\n"
	    << "v1:  " << v1 << "\n"
            << "v2:  " << v2 << "\n"
            << "v3:  " << v3 << "\n";
  
  err_cnt++;

  return false;
  }


static answer
timed_run(min3sort_fun m3s, const input_vectors & vecs, time_t & et) {

  // Run the 3-sorting procedure m3s against the given input vectors, returning
  // the result returned by the sort.  Also return in et the sorting time in
  // usecs.

  struct timeval s, e;

  gettimeofday(&s, NULL);
  const answer a = m3s(vecs.v0, vecs.v1, vecs.v2);
  gettimeofday(&e, NULL);

  if (s.tv_sec < e.tv_sec) { 
    e.tv_sec--;
    e.tv_usec += 1000000; 
    }
  et = (e.tv_sec - s.tv_sec)*1000000 + (e.tv_usec - s.tv_usec);

  return a;
  }


// $Log: main.cc,v $
// Revision 1.4  2002/11/11 02:52:48  rclayton
// Define an inserter for int vectors.
//
// Revision 1.3  2002/11/11 02:32:03  rclayton
// Put in all the documentation.
//


syntax highlighted by Code2HTML, v. 0.9