/** \file oastreaming.cpp

 C++ program: oastreaming

 oastreaming can calculate series of non-isomorphic orthogonal arrays

 Author: Pieter Eendebak <pieter.eendebak@gmail.com>, (C) 2014

 Copyright: See COPYING file that comes with this distribution
*/

#include <errno.h>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#ifdef _WIN32
#else
#include <stdbool.h>
#include <unistd.h>
#endif

#include "anyoption.h"
#include "extend.h"
#include "lmc.h"
#include "mathtools.h"
#include "strength.h"
#include "tools.h"

inline void print_progress (int csol, int ninput, int nextensions, double Tstart, colindex_t col) {
        time_t seconds;
        struct tm *tminfo;

        time (&seconds);
        tminfo = localtime (&seconds);
        log_print (QUIET, "Extending column %d of array %i/%i ~ %4.1f%% (total collected %d), time %.2f s, %s",
                   col + 1, csol, (int)ninput, 100.0 * ((float)csol) / (float)ninput, (int)nextensions,
                   (get_time_ms () - Tstart), asctime (tminfo));
}

/// parse the command line options for the program
AnyOption *parseOptions (int argc, char *argv[], algorithm_t &algorithm) {
        AnyOption *opt = new AnyOption;

        /* parse command line options */
        opt->setFlag ("help", 'h'); /* a flag (takes no argument), supporting long and short form */
        opt->setOption ("loglevel", 'l');
        opt->setFlag ("x", 'x'); /* special debugging flag */
        opt->setOption ("restart", 'r');
        opt->setOption ("oaconfig", 'c');    /* file that specifies the design */
        opt->setOption ("output", 'o');      /* prefix for output files */
        opt->setFlag ("generate", 'g');      /* only generate extensions, do not perform LMC check */
        opt->setFlag ("coptions");           /* only generate extensions, do not perform LMC check */
        opt->setOption ("format", 'f');      /* format to write to */
        opt->setOption ("mode", 'm');        /* algorithm method */
        opt->setOption ("maxk", 'K');        /* max number of columns to extend to */
        opt->setOption ("rowsymmetry", 'R'); /* max number of columns to extend to */

        opt->setOption ("initcolprev", 'I'); /* algorithm method */

        opt->addUsage ("Orthonal Arrays: extend orthogonal arrays");
        opt->addUsage ("Usage: oastreaming [OPTIONS]");

        opt->addUsage ("");
        opt->addUsage (" -h  --help  			Prints this help ");
        opt->addUsage (" --coptions			Show compile time options used ");
        opt->addUsage (" -r [FILE]  --restart [FILE]	Restart with results file ");
        opt->addUsage (" -l [LEVEL] --loglevel [LEVEL]	Set loglevel to number ");
        opt->addUsage (" -c [FILE]  --oaconfig [FILE]	Set file with design specification");
        opt->addUsage (" -g  --generate [FILE]		Only generate arrays, do not perform LMC check");
        opt->addUsage ("  --rowsymmetry [VALUE]		Use row symmetry in generation");
        opt->addUsage (" -o [STR]  --output [FILE]	Set prefix for output (default: result) ");
        opt->addUsage (" -f [FORMAT]			Output format (default: TEXT, or BINARY) ");
        opt->addUsage (" --initcolprev [INTEGER]	Initialization method of new column (default: 1)");
        opt->addUsage (
            " --maxk [INTEGER] Maximum number of columns to exten to (default: extracted from config file) ");

        std::string ss = printfstring (" -m [MODE]			Algorithm (") + algorithm_t_list () + ")\n";
        opt->addUsage (ss.c_str ());
        opt->addUsage ("");
        opt->addUsage ("Example: ./oastreaming -r inputarrays.oa -l 2");

        opt->processCommandArgs (argc, argv);

        if (opt->getValue ("mode") != NULL || opt->getValue ('m') != NULL) {
                int vv = atoi (opt->getValue ('m')); // set custom loglevel
                algorithm = (algorithm_t)vv;
        } else
                algorithm = MODE_AUTOSELECT;

        return opt;
}

/**
 * @brief Main function for oaextendmpi and oaextendsingle
 * @param argc
 * @param argv[]
 * @return
 */
int main (int argc, char *argv[]) {

        double Tstart = get_time_ms ();

        algorithm_t algorithm = MODE_INVALID;
        AnyOption *opt = parseOptions (argc, argv, algorithm);
        print_copyright ();

        if (opt->getFlag ("help") || (opt->getValue ("coptions") != NULL)) {
                if (opt->getFlag ("help"))
                        opt->printUsage ();
                if (opt->getValue ("coptions") != NULL)
                        print_options (cout);

                delete opt;
                return 0;
        }

        int loglevel = opt->getIntValue ('l', NORMAL);
        setloglevel (loglevel);

        int userowsymm = opt->getIntValue ("rowsymmetry", 1);
        int maxk = opt->getIntValue ("maxk", 100000);
        int initcolprev = opt->getIntValue ("initcolprev", 1);

        bool restart = false;
        if (opt->getValue ("restart") != NULL || opt->getValue ('r') != NULL) {
                restart = true;
        } else {
                printf ("oastreaming: please enter an input file with the -r option\n");
                exit (1);
        }
        std::string restartfile = opt->getValue ('r');

        const char *oaconfigfile = opt->getStringValue ('c', "oaconfig.txt");
        const char *resultprefix = opt->getStringValue ('o', "result");

        arrayfile::arrayfilemode_t mode = arrayfile_t::parseModeString (opt->getStringValue ('f', "B"));

        myassert (mode != ATEXT, "error: cannot use text file in streaming mode");

        OAextend oaextend;
        oaextend.setAlgorithm (algorithm);
        oaextend.extendarraymode = OAextend::extendarray_mode_t::STOREARRAY;

        // J5_45
        int xx = opt->getFlag ('x');
        if (xx)
                oaextend.j5structure = J5_45;

        if (userowsymm == 0) {
                oaextend.use_row_symmetry = userowsymm;
                printf ("use row symmetry -> %d\n", oaextend.use_row_symmetry);
        }
        if (opt->getFlag ('g')) {
                std::cout << "only generating arrays (no LMC check)" << endl;
                oaextend.checkarrays = 0;
        }

        logstream (QUIET) << "#time start: " << currenttime () << std::endl;

        arraydata_t *ad;
        ad = readConfigFile (oaconfigfile);
        if (ad == 0)
                return 1;

        log_print (NORMAL, "Using design file: %s (runs %d, strength %d)\n", oaconfigfile, ad->N, ad->strength);

        if (oaextend.getAlgorithm () == MODE_AUTOSELECT) {
                oaextend.setAlgorithmAuto (ad);
        }

        if (initcolprev == 0) {
                log_print (NORMAL, "setting oaextend.init_column_previous to %d\n", INITCOLUMN_ZERO);
                oaextend.init_column_previous = INITCOLUMN_ZERO;
        }

        log_print (SYSTEM, "using algorithm %d (%s)\n", oaextend.getAlgorithm (),
                   oaextend.getAlgorithmName ().c_str ());
        if (log_print (NORMAL, "")) {
                std::cout << oaextend.__repr__ ();
        }

        /*-----------MAIN EXTENSION LOOP-------------*/

        arrayfile_t inputarrays (restartfile.c_str ());

        // check that oaconfig agrees with loaded arrays
        if (ad->N != inputarrays.nrows) {
                printf ("error: oaconfig does not agree with loaded arrays! (specified N %d)\n", ad->N);
                exit (1);
        }

        colindex_t col_start = inputarrays.ncols;
        int current_col = col_start;
        printf ("## oastreaming: extension of %d arrays from %d to %d columns\n", inputarrays.narrays, col_start + 1,
                col_start + 2);
        time_t seconds;

        fflush (stdout);
        arraydata_t *adcol = new arraydata_t (ad, current_col + 1);

        string fname = resultprefix;
        fname += "-streaming";
        fname += "-" + oafilestring (adcol);
        logstream (NORMAL) << "oaextend: streaming mode: create file " << fname << std::endl;
        int nb = arrayfile_t::arrayNbits (*ad);
        oaextend.storefile.createfile (fname, adcol->N, adcol->ncols, -1, mode, nb);

        log_print (SYSTEM, "Starting with column %d (total time: %.2f [s])\n", current_col + 1,
                   get_time_ms () - Tstart);
        int nr_extensions = 0;

        arraylist_t extensionsdummy;
        for (int input_array_idx = 0; input_array_idx < inputarrays.narrays; input_array_idx++) {
                array_link array (inputarrays.nrows, inputarrays.ncols, input_array_idx);
                inputarrays.read_array (array);

                long nextensions = oaextend.storefile.narraycounter;
                print_progress (input_array_idx, inputarrays.narrays, nextensions, Tstart, current_col);
                nr_extensions += extend_array (array, adcol, current_col, extensionsdummy, oaextend);
        }

        long nextensions = oaextend.storefile.narraycounter;
        log_print (SYSTEM, "Done with column %i, total of %i solutions (time %.2f s))\n", current_col + 1,
                   (int)nextensions, get_time_ms () - Tstart);

        oaextend.storefile.closefile ();

        /* report time */
        time (&seconds);
        struct tm *tminfo;
        tminfo = localtime (&seconds);
        log_print (SYSTEM, "TIME: %.2f s, %s", get_time_ms () - Tstart, asctime (tminfo));
        logstream (QUIET) << "#time end: " << currenttime () << std::endl;
        logstream (QUIET) << "#time total: " << printfstring ("%.1f", get_time_ms () - Tstart) << " [s]" << std::endl;

        delete adcol;
        delete ad;
        delete opt;

        return 0;
}

// kate: indent-mode cstyle; indent-width 4; replace-tabs off; tab-width 4;
