/**
 * @file Util.hh
 *
 * Miscellaneous utilities
 *
 * Author: Peter Helfer
 * Date: 2012-01-06
 */

#ifndef UTIL_HH
#define UTIL_HH

#include <glob.h>
#include <vector>
using std::vector;
#include <string.h>
#include <string>
#include <bitset>
using std::string;
#include "fmt/format.h"

#include "Trace.hh"

namespace Util {
    /**
     * Test for oddness or evenness
     */
    inline bool isOdd(uint i)  { return i % 2 == 1; }
    inline bool isEven(uint i) { return i % 2 == 0; }

    /**
     * Test if a string consists of digits only
     */
    inline bool isDigitsOnly(const std::string & s)
    {
        for (uint i = 0; i < s.size(); i++) {
            if (!isdigit(s[i])) return false;
        }
        return true;
    }    
    
    /**
     * Initialize rand
     */
    void initRand();

    /**
     * Generate a random integer in [min, max[
     */
    int randInt(int min, int max);
    
    /**
     * Generate a random double in [min, max] or ]min, max[
     * @param min Lower interval endpoint
     * @param max Upper interval endpoint
     * @param open If true sample from ]min, max[, else [min, max]
     */
    double randDouble(double min, double max, bool open = false);

    /**
     * Create a random permutation of the integers [min, max[
     */
    vector<int> randPerm(int min, int max);
    
    /**
     * Create a random permutation of the integers [0, n[
     */
    inline vector<int> randPerm(int n) {
        return randPerm(0, n);
    }
    
    /**
     * Create a random set of n integers in the range [0, max[
     * May contain duplicates.
     */
    vector<int> randIntList(uint n, int max);

    /**
     * Create a random set of n unique integers in the range [min, max[
     * No duplicates.
     */
    vector<int> randUniqueIntList(int n, int min, int max);

    /**
     * Create a random set of n unique integers in the range [0, max[
     * No duplicates.
     */
    inline vector<int> randUniqueIntList(int n, int max) {
        return randUniqueIntList(n, 0, max);
    }

    /**
     * Create a random set of n unique uints in the range [0, max[
     * No duplicates.
     */
    vector<uint> randUniqueUintList(int n, uint max);


    /**
     * Create a random set of n doubles in the range [min, max] or (min, max)
     * May contain duplicates.
     * @param min Lower interval endpoint
     * @param max Upper interval endpoint
     * @param open If true sample from (min, max), else [min, max]
     */
    vector<double> randDoubleList(uint n, double min, double max, bool open = false );

    /**
     * Create a random set of n doubles from the provided domain
     * May contain duplicates.
     */
    vector<double> randDoubleList(uint n, vector<double> &domain);

    /**
     * Create a random set of n unique doubles from the provided domain
     * No duplicates.
     */
    vector<double> randUniqueDoubleList(uint n, vector<double> &domain);

    /**
     * Create a binary string representation of an integer with
     * a specified number of bits, e.g. 13, 5 --> "01101".
     * @param value The number to represent
     * @param len Number of bits, default is the number of bits in T
     * @param zeroChar character for representing a zero bit
     * @param oneChar character for representing a one bit
     */
    template<class T> std::string intToBinStr(
        T i,
        uint width = 0,
        char zero = char('0'),
        char one = char('1'))
    {
        uint w = (width != 0) ? width : 8 * sizeof(i);
        std::string s = std::bitset<8 * sizeof(i)>(i).to_string(zero, one);
        return s.substr(s.length() - w);
    }
    /*
     Less C++ style version 
    inline string intToBinStr(int value, int len, char zeroChar, char oneChar)
    {
        char buf[len+1];
        buf[len] = 0;
        for (int i = 0; i < len; i++) {
            buf[len - i - 1] = ((value >> i) & 1) ? oneChar : zeroChar;
        }
        return buf;
    }
    */

    /**
     * Create a string of length len where each character is
     * randomly selected from charset.
     * @param charset Set of characters to use
     * @param len Length of resulting string
     */
    string randStr(const char* charset, int len);

    /**
     * Print a usage message, optionally preceded by an error message.
     * @param syntax Syntax message
     * @param fmt ... printf-style error message
     */
    void usage(const char *syntax, const char *fmt, ...);

    /**
     * Print a usage message, optionally preceded by an error message,
     * then exit with status EXIT_FAILURE.
     * @param syntax Syntax message
     * @param fmt ... printf-style error message
     */
    void usageExit(const char *syntax, const char *fmt, ...);

    /**
     * return the min or max of two or three values
     * TODO: rewrite with variadic template
     */
    template<class T> T     min(const T &a, const T &b) { return (a < b) ? a : b; }
    template<class T> T     max(const T &a, const T &b) { return (a > b) ? a : b; }

    template<class T> T     min(const T &a, const T &b, const T &c) {
        return (a < b) ? min(a, c) : min(b, c);
    }
    template<class T> T     max(const T &a, const T &b, const T &c) {
        return (a > b) ? max(a, c) : max(b, c);
    }
    
    /**
     * If value is within [min:max] return it, otherwise return
     * the nearest of min or max.
     */
    template<class T> T bracket(const T value, const T min, const T max)
    {
        return (value < min) ? min : (value > max) ? max : value;
    }

    /**
     * Confine a variable to a range
     */
    template<class T> void confine(T &val, const T &min, const T &max)
    {
        if (val < min) {
            val = min;
        } else if (val > max) {
            val = max;
        }
    }

    /**
     * Chick if a variable is in a range
     */
    template<class T> bool isInRange(T &val, const T &min, const T &max)
    {
        return (val >= min) && (val <= max);
    }

    /**
     * Swap the values of two variables
     */
    template<class T> void swap(T &a, T &b) { T t = a; a = b; b = t; }

    /**
     * Create a copy of a string with initial and final whitespace stripped
     */
    string wstrip(const string &s);

    /**
     * Whether a string consist entirely of whitespace
     */
    inline bool isBlank(const string &s) { return wstrip(s).size() == 0; }
    inline bool isBlank(const char *s) {return s == NULL || isBlank(string(s)); }

    /**
     * Case-independent string compare
     */
    inline int strCiCmp(const string &s1, const string &s2)
    {
        return strcasecmp(s1.c_str(), s2.c_str());
    }

    /**
     * Case-independent string equality test
     */
    inline bool strCiEq(const string &s1, const string &s2)
    {
        return strCiCmp(s1, s2) == 0;
    }

    /**
     * Case-dependent string equality test
     */
    inline bool strEq(const string &s1, const string &s2)
    {
        return strcmp(s1.c_str(), s2.c_str()) == 0;
    }

    /**
     * Chop off terminating linefeed of C string (in place)
     */
    char *chop(char *str);

    /**
     * Plural suffix for printf formats
     */
    inline const char *plural(int i)
    {
        return (i == 1) ? "" : "s";
    }

    /**
     * Plural form for printf formats
     */
    inline const char *plural(int i, const char *sing, const char *plur)
    {
        return (i == 1) ? sing : plur;
    }

    /**
     * double compare function, suitable for qsort
     */
    inline int compareDoubles(const void* d1, const void* d2)
    {
        double diff = *((double *)d1) - *((double *)d2);
        return (diff > 0.0) ? 1 : (diff < 0.0) ? -1 : 0;
    }

    /*
     * Build a repeat of a given string
     *
     * Efficient algortihm found at
     * https://codereview.stackexchange.com/questions/114295
     */
    inline string repeatStr(std::string const &str, std::size_t n)
    {
        if (n == 0) {
            return {};
        }

        if (n == 1 || str.empty()) return str;

        if (str.size() == 1) {
            return std::string(n, str[0]);
        }

        std::string result = str;
        result.reserve(str.size() * n);

        std::size_t m = 2;
        for (; m <= n; m *= 2) {
            result += result;
        }

        n -= m/2;

        result.append(result.c_str(), n * str.size());

        return result;
    }

    /**
     * Generate [hh:]mm:ss representation of a time interval.
     * @param seconds Time interval in seconds
     * @param showZeroHours whether to show zero hours
     */
    string hms(uint seconds, bool showZeroHours = true);

    /**
     * Generate [hh:]mm:ss.sss representation of a time interval.
     * @param seconds Time interval in seconds
     * @param milliseconds Additional milliseconds (negative is ok)
     * @param showZeroHours whether to show zero hours
     */
    string hmsm(uint seconds, int milliseconds, bool showZeroHours);

    /**
     * Tokenize a string: if str != NULL return pointer to first token,
     * NUL-terminated. If str == NULL, return next token from string being
     * parsed.
     * 
     * If singleSep is true, then consecutive separator characters separate
     * empty tokens. Default behavior is like strtok: strtok treats two or
     * more consecutive separator characters as a single separator.
     *
     * Unlike strtok, is reentrant and threadsafe.
     *
     * Like strtok, destroys str by overwriting the separators with NULs.
     * 
     * @param str String to tokenize
     * @param sep Separator
     * @param memptr Memory pointer provided by (and not used by) the context
     * @singleSep If true, then consecutive separators separate empty tokens
     * @return Next token, or NULL if no more tokens
     */
    char *tok(char *str, const char *sep, char **memptr, bool singleSep = false);

    /**
     * Convert string to int
     * @param errMsg will be non-empty if there's a problem
     */
    int strToInt(string s, string &ErrMsg);

    /**
     * Convert string to uint
     * @param errMsg will be non-empty if there's a problem
     */
    uint strToUint(string s, string &ErrMsg);

    /**
     * Convert string to double
     * @param errMsg will be non-empty if there's a problem
     */
    double strToDouble(string s, string &errMsg);
    
    /**
     * Convert arithmetic expression to double
     * @param errMsg will be non-empty if there's a problem
     */
    double exprToDouble(string s, string &errMsg);
    
    /**
     * Convert string to bool
     * @param errMsg will be non-empty if there's a problem
     */
    bool strToBool(string s, string &errMsg);
    
    /**
     * Tokenize a c-string
     * @param str String to tokenize
     * @param sepChars Separator characters
     * @errMsg set to non-NULL if error encountered
     * @param quoteChars delimit string tokens
     * @param tokChars Single-character tokens (self-delimiting)
     * @singleSep If true, then consecutive separators separate empty tokens
     * @return The tokens
     */
    std::vector<string> tokenize(
        const char *str, 
        const char *sepChars, 
        const char *&errMsg,
        const char *quoteChars = "",
        const char *tokChars = "", 
        bool        singleSep = false);
    
    /**
     * Tokenize a std::string
     * @param str String to tokenize
     * @param sepChars Separator characters
     * @errMsg set to non-empty if error encountered
     * @param quoteChars delimit string tokens
     * @param tokChars Single-character tokens (self-delimiting)
     * @singleSep If true, then consecutive separators separate empty tokens
     * @return The tokens
     */
    inline std::vector<string> tokenize(
        const string str, 
        const string sepChars, 
        string       &errMsg,
        const string quoteChars = "",
        const string tokChars = "", 
        bool         singleSep = false)
    {
        const char *err = NULL;
        std::vector<string> tokens = tokenize(str.c_str(),
                                              sepChars.c_str(), 
                                              err,
                                              quoteChars.c_str(), 
                                              tokChars.c_str(),
                                              singleSep);
        if (err != NULL) {
            errMsg = err;
        }
        return tokens;
    }

    /**
     * Concatenate tokens, separated by sep
     * @param tokens The tokens
     * @param sep Separator string
     */
    inline string untokenize(std::vector<string> tokens, string sep = " ")
    {
        uint n = tokens.size();
        string result;
        for (uint i = 0; i < n -1; i++) {
            result += tokens[i];
            result += sep;
        }
        result += tokens[n-1];
        return result;
    }

    /**
     * std::string wrapper around glob(3): find pathnames matching a pattern
     * @param pat A pathname pattern as specified in glob(7).
     */
    inline std::vector<std::string> glob(const string& pat){
        glob_t glob_result;
        glob(pat.c_str(), GLOB_TILDE, NULL, &glob_result);
        std::vector<string> ret;
        for(unsigned int i = 0; i < glob_result.gl_pathc; ++i){
            ret.push_back(string(glob_result.gl_pathv[i]));
        }
        globfree(&glob_result);
        return ret;
    }

    /**
     * Option argument types, for use when calling parseOpts. The end comments
     * indicate the type of variable that the corresponding argPtr must point to
     * for each value of optArgType.
     */
    const int OPTARG_NONE = 0;	// bool
    const int OPTARG_STR  = 1;	// char *
    const int OPTARG_INT  = 2;	// int
    const int OPTARG_UINT = 3;	// uint
    const int OPTARG_DBLE = 4;	// double
    const int OPTARG_EXPR = 5;	// arith expr

    /**
     * Option specification for parseOpts and parseOptsUsage
     * 
     * For options that don't take an argument, argType should be set
     * to OPTARG_NONE and argPtr should point to a bool, which will be
     * set to true if the option is present.
     * 
     * argName is used by parseOptsUsage, but not by parseOpts
     */
    typedef struct {
        const char *optName;   // option name as specified on cmdline
        int         argType;   // option argument type
        void       *argPtr;    // pointer to variable that will receive arg value
        const char *argName;   // arg value name, used for usage message only
        const char *descr;     // Description, used for usage message only
    } ParseOptSpec;
    
    /**
     * Parse command line options, using getopt_long_only (3)
     *
     * Options may be single- or multi-character, and may (in either case) be
     * specified by either a single or a double dash. For example, if the
     * options "v" and "verbosity" are both specified, than any of -v, --v,
     * -verbosity and --verbosity will work, and the argument, when present, may
     * be separated from the option name either by whitespace or by a =.
     * Furthermore, long option names may be abbreviated, as long as the
     * abbreviation is unique, so "-verb 5" will also work, unless there is
     * another option with a name that begins with 'verb'.
     *
     * Concatenation of single-character options (like ps -eax) is not supported.
     * 
     * @param argc The argument count.
     * @param argv The argument vector.
     * @param optSpecs Option specifiers
     * @return 0 if all went well, -1 otherwise 
     */
    int parseOpts(int argc, char *argv[], std::vector<ParseOptSpec> optSpecs);

    /**
     * Generate a "usage" message given a program name and a vector of
     * parseOpts option specifications.
     * 
     * @param pname The program name
     * @param optSpecs Option specifiers
     * @param vertical If true, display option list vertically
     * @param nonFlags Cmd line args other than flags
     * @return 0 if all went well, -1 otherwise
     */
    string parseOptsUsage(
        const char *pname,
        std::vector<ParseOptSpec> optSpecs,
        bool vertical = false,
        std::vector<string> nonFlags = std::vector<string>());

    // Initialize binomial coefficient table to specified dimensions.
    // Note: if not called explicitly, then binom() will call it as needed.
    //
    void initBinom(uint N, uint K);

    // Look up a binomial coefficient
    //
    uint binom(uint n, uint k);


    /**
     * Quick fmtlib formats for printing out matrices
     * (printf equivalents in comments)
     */
    // TODO: width and precision as arguments
    inline const char *tableFmt(double)       { return "{:8.2f}"; } // "%#6.2f"
    inline const char *tableFmt(int)          { return "{:6d}"; }   // "%6d"
    inline const char *tableFmt(uint)         { return "{:6d}"; }   // "%6d"
    inline const char *tableFmt(const char *) { return "{}"; }      // "%s"

    /**
     * String representation of a matrix of numbers or C strings
     */
    template<class T> string matrixToStr(vector<vector<T>> &m)
    {
        string s;
        for (uint i = 0; i < m.size(); i++) {
            for (uint j = 0; j < m[0].size(); j++) {
                T &val = m[i][j];
                string valStr = fmt::format(tableFmt(val), val);
                s += valStr;
            }
            s += "\n";
        }
        return s;
    }

    /**
     * Element-wise vector and matrix operations
     */
    enum Operation {
        ADD,
        SUB,
        MUL,
        DIV,
        MIN,
        MAX,
        AVG,
        STDEVP,
        STDEVS,
        STERR
    };

    /**
     * Apply an operation to corresponding elements of two vectors
     */
    template<class T> vector<T> vectorOp(
        vector<T> v1,
        vector<T> v2,
        Operation op)
    {
        ABORT_IF(v1.size() != v2.size(), "vectors must be of equal size", 0);

        vector<T> r;
        for (uint i = 0; i < v1.size(); i++) {
            T val;
            switch (op) {
                case ADD: val = v1[i] + v2[i]; break;
                case SUB: val = v1[i] - v2[i]; break;
                case MUL: val = v1[i] * v2[i]; break;
                case DIV: val = v1[i] / v2[i]; break;
                case MIN: val = min(v1[i], v2[i]); break;
                case MAX: val = max(v1[i], v2[i]); break;
                default: TRACE_FATAL("Invalid operation", 0);
            }
            r.push_back(val);
        }
        return r;
    }

    /**
     * Apply an operation with a scalar to each element of a vector
     */
    template<class T> vector<T> vectorOp(
        vector<T> v,
        T a,
        Operation op)
    {
        vector<T> r;
        for (uint i = 0; i < v.size(); i++) {
            T val;
            switch (op) {
                case ADD: val = v[i] + a; break;
                case SUB: val = v[i] - a; break;
                case MUL: val = v[i] * a; break;
                case DIV: val = v[i] / a; break;
                case MIN: val = min(v[i], a); break;
                case MAX: val = max(v[i], a); break;
                default: TRACE_FATAL("Invalid operation", 0);
            }
            r.push_back(val);
        }
        return r;
    }

    template<class T, class U> vector<T> vectorAdd(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, ADD);
    }

    template<class T, class U> vector<T> vectorSub(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, SUB);
    }

    template<class T, class U> vector<T> vectorMul(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, MUL);
    }

    template<class T> vector<T> vectorSquare(
        vector<T> v)
    {
        return vectorMul(v, v);
    }

    template<class T, class U> vector<T> vectorDiv(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, DIV);
    }

    template<class T, class U> vector<T> vectorMin(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, MIN);
    }

    template<class T, class U> vector<T> vectorMax(
        vector<T> v,
        U a)
    {
        return vectorOp(v, a, MAX);
    }

    /**
     * Apply an operation to corresponding elements of two matrices
     */
    template<class T> vector<vector<T>> matrixOp(
        vector<vector<T>> m1,
        vector<vector<T>> m2,
        Operation op)
    {
        ABORT_IF(m1.size() != m2.size(), "matrices must be equal size", 0);
        vector<vector<T>> r;
        for (uint i = 0; i < m1.size(); i++) {
            vector<T> row;
            switch (op) {
                case ADD: row = vectorAdd(m1[i], m2[i]); break;
                case SUB: row = vectorSub(m1[i], m2[i]); break;
                case MUL: row = vectorMul(m1[i], m2[i]); break;
                case DIV: row = vectorDiv(m1[i], m2[i]); break;
                case MIN: row = vectorMin(m1[i], m2[i]); break;
                case MAX: row = vectorMax(m1[i], m2[i]); break;
                default: TRACE_FATAL("Invalid operation", 0);
            }
            r.push_back(row);
        }
        return r;
    }

    /**
     * Apply an operation with a scalar to each element of a matrix
     */
    template<class T> vector<vector<T>> matrixOp(
        vector<vector<T>> m,
        T a,
        Operation op)
    {
        vector<vector<T>> r;
        for (uint i = 0; i < m.size(); i++) {
            vector<T> row;
            switch (op) {
                case ADD: row = vectorAdd(m[i], a); break;
                case SUB: row = vectorSub(m[i], a); break;
                case MUL: row = vectorMul(m[i], a); break;
                case DIV: row = vectorDiv(m[i], a); break;
                case MIN: row = vectorMin(m[i], a); break;
                case MAX: row = vectorMax(m[i], a); break;
                default: TRACE_FATAL("Invalid operation", 0);
            }
            r.push_back(row);
        }
        return r;
    }
    
    template<class T, class U> vector<vector<T>> matrixAdd(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, ADD);
    }

    template<class T, class U> vector<vector<T>> matrixSub(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, SUB);
    }

    template<class T, class U> vector<vector<T>> matrixMul(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, MUL);
    }

    template<class T> vector<vector<T>> matrixSquare(
        vector<vector<T>> m)
    {
        return matrixMul(m, m);
    }

    template<class T, class U> vector<vector<T>> matrixDiv(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, DIV);
    }

    template<class T, class U> vector<vector<T>> matrixMin(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, MIN);
    }

    template<class T, class U> vector<vector<T>> matrixMax(
        vector<vector<T>> m,
        U a)
    {
        return matrixOp(m, a, MAX);
    }
}    

/**
 * Number of elements in a statically allocated array
 */
#define NUM_ELEM(x) (sizeof(x) / sizeof(x[0]))

/**
 * Test if pointer or object is of a specified class
 * (I wonder if there's a way to do this with templates?)
 */
#define IS_PTR_TO(pointer, type) (dynamic_cast<type *>(pointer) != NULL)
#define IS_OF_CLASS(obj, type)   (dynamic_cast<type *>(&obj) != NULL)

#endif // UTIL_HH