#ifndef SEQLIB_UTILS_H
#define SEQLIB_UTILS_H

#include <algorithm>
#include <cmath>
#include <ctime>
#include <sstream>
#include <stdio.h>
#include <string>
#include <time.h>
#include <unistd.h>
#include <vector>

#include "SeqLib/SeqLibCommon.h"


#if __cplusplus > 199711L
#include <memory>
#include <unordered_map>
#include <unordered_set>
#define SeqHashMap std::unordered_map
#define SeqHashSet std::unordered_set
#define SeqPointer std::shared_ptr
#define HAVE_C11 1
#else

#ifdef __APPLE__
#include <memory>
#include <unordered_map>
#include <unordered_set>
#define SeqHashMap std::unordered_map
#define SeqHashSet std::unordered_set
#define SeqPointer std::shared_ptr
#else
#include <tr1/memory>
#include <tr1/unordered_map>
#include <tr1/unordered_set>
#define SeqHashMap std::tr1::unordered_map
#define SeqHashSet std::tr1::unordered_set
#define SeqPointer std::tr1::shared_ptr
#endif
#endif

namespace SeqLib
{

    template <typename T>
    inline std::string tostring(T d)
    {
        std::stringstream ss;
        ss << d;
        return ss.str();
    }

    /** Check if a file is readable and exists
     * @param name Name of a file to test
     * @return File is readable and exists
     */
    inline bool read_access_test(const std::string& name)
    {
        return (access(name.c_str(), R_OK) == 0);
    }

    /** Format an integer to include commas
     * @param data Number to format
     * @return String with formatted number containing commas
     */
    template <typename T>
    inline std::string AddCommas(T data)
    {
        std::stringstream ss;
        ss << data;
        std::string s = ss.str();
        if (s.length() > 3)
            for (int i = s.length() - 3; i > 0; i -= 3)
                s.insert(i, ",");
        return s;
    }

    /** Display the runtime (CPU and Wall)
     *
     * @param start Running timer
     * @return Time formatted as "CPU: XmYs Wall: XmYs"
     * @note Does not work on OSX or Windows (returns "not configured")
     */
    inline std::string displayRuntime(
#ifndef __APPLE__
        const timespec start
#endif
    )
    {

#ifndef __APPLE__
        struct timespec finish;
        clock_gettime(CLOCK_MONOTONIC, &finish);
        double elapsed = (finish.tv_sec - start.tv_sec);
        int t = clock() / CLOCKS_PER_SEC;
        int min = (int)std::floor(elapsed / 60.0);
        int sec = (int)(elapsed - min * 60);
        char buffer[100];
        sprintf(buffer, "CPU: %4dm%02ds Wall: %4dm%02ds", (int)floor(((double)t) / 60.0), t % 60, min, sec);
        buffer[99] = '\0';
        return std::string(buffer);
#else
        return "--- time not configured for apple\n";
#endif
    }

    /** Reverse complement in-place sequence containg upper/lower case ACTGN
     * @param a Sequence to be reverse complmented
     */
    inline void rcomplement(std::string& a)
    {

        std::reverse(&a[0], &a[a.size()]);
        std::string::iterator it = a.begin();
        for (; it != a.end(); it++)
            *it = RCOMPLEMENT_TABLE[(unsigned char)*it];
    }


    /** Calculate the percentage and return as integer
     * @param numer Numerator
     * @param denom Denominator
     * @return Integer with the percentage floored
     */
    template <typename T>
    inline int percentCalc(T numer, T denom)
    {
        if (denom <= 0)
            return 0;
        int perc = numer * 100 / denom;
        // int perc  = static_cast<int>(floor((float)numer / (float)denom * 100.0));
        return perc;
    }

    /** Remove substrings from a string
     * @param toscrub Input string to clean
     * @param toremove Substring to remove from input
     * @return Scrubbed string
     */
    inline std::string scrubString(const std::string& toscrub, const std::string& toremove)
    {
        if (toscrub.empty() || toremove.empty())
            return toscrub;

        std::string::size_type i = toscrub.find(toremove);
        if (i == std::string::npos)
            return toscrub;

        std::string ts = toscrub;
        while (i != std::string::npos)
        {
            ts.erase(i, toremove.length());
            i = ts.find(toremove);
        }
        return ts;
    }

    // Generate a weighed random integer
    // @param cs Weighting for each integer (values must sum to one)
    // @return Random integer bounded on [0,cs.size())
    //
    // int weightedRandom(const std::vector<double>& cs);

} // namespace SeqLib

#endif
