#include "htio2/Cast.h"
#include "htio2/StringUtil.h"

#include <stdlib.h>

using namespace std;

namespace htio2
{

string _prepare_input_(const std::string& input)
{
    size_t i_begin = input.find_first_not_of(" \t");
    return to_lower_case(input.substr(i_begin));
}

template <>
bool from_string<bool>(const std::string& input, bool& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "yes" || input_lc == "true" || input_lc == "y" || input_lc == "t" || input_lc == "1")
    {
        output = true;
        return true;
    }
    else if (input_lc == "no" || input_lc == "false" || input_lc == "n" || input_lc == "f" || input_lc == "0")
    {
        output = false;
        return true;
    }
    else
        return false;
}

// cast to signed integers
#define FROM_STRING_INT_IMPL(_out_type_, _func_) \
    char* p_end = 0;\
    _out_type_ tmp = _func_(input.c_str(), &p_end, 0);\
    if (p_end != input.c_str())\
    {\
        output = tmp;\
        return true;\
    }\
    else\
        return false;\

#define FROM_STRING_FLOAT_IMPL(_out_type_, _func_)\
    char* p_end = 0;\
    _out_type_ tmp = _func_(input.c_str(), &p_end);\
    if (p_end != input.c_str())\
    {\
        output = tmp;\
        return true;\
    }\
    else\
        return false;\

template <>
bool from_string<short>(const std::string& input, short& output)
{
    FROM_STRING_INT_IMPL(short, strtol);
}

template <>
bool from_string<int>(const std::string& input, int& output)
{
    FROM_STRING_INT_IMPL(int, strtol);
}

template <>
bool from_string<long>(const std::string& input, long& output)
{
    FROM_STRING_INT_IMPL(long, strtol);
}

template <>
bool from_string<long long>(const std::string& input, long long& output)
{
    FROM_STRING_INT_IMPL(long long, strtoll);
}

// cast to unsigned integers
template <>
bool from_string<unsigned short>(const std::string& input, unsigned short& output)
{
    FROM_STRING_INT_IMPL(unsigned short, strtoul);
}

template <>
bool from_string<unsigned int>(const std::string& input, unsigned int& output)
{
    FROM_STRING_INT_IMPL(unsigned int, strtoul);
}

template <>
bool from_string<unsigned long>(const std::string& input, unsigned long& output)
{
    FROM_STRING_INT_IMPL(unsigned long, strtoul);
}

template <>
bool from_string<unsigned long long>(const std::string& input, unsigned long long& output)
{
    FROM_STRING_INT_IMPL(unsigned long long, strtoull);
}

// cast to floating points
template <>
bool from_string<float>(const std::string& input, float& output)
{
    FROM_STRING_FLOAT_IMPL(float, strtof);
}

template <>
bool from_string<double>(const std::string& input, double& output)
{
    FROM_STRING_FLOAT_IMPL(double, strtod);
}

template <>
bool from_string<long double>(const std::string& input, long double& output)
{
    FROM_STRING_FLOAT_IMPL(long double, strtold);
}

// cast to HTIO enum types
template<>
bool from_string<Strand>(const std::string& input, Strand& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "forward" || input_lc == "fwd" || input_lc == "1")
    {
        output = STRAND_FWD;
        return true;
    }
    else if (input_lc == "reverse" || input_lc == "rev" || input_lc == "-1")
    {
        output = STRAND_REV;
        return true;
    }
    else if (input_lc == "unknown" || input_lc == "0")
    {
        output = STRAND_UNKNOWN;
        return true;
    }
    else
        return false;
}

template<>
bool from_string<SeqFormat>(const std::string& input, SeqFormat& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "fastq" || input_lc == "fq")
    {
        output = FORMAT_FASTQ;
        return true;
    }
    else if (input_lc == "fasta" || input_lc == "fas" || input_lc == "fa" || input_lc == "fna")
    {
        output = FORMAT_FASTA;
        return true;
    }
    else
        return false;
}

template<>
bool from_string<HeaderFormat>(const std::string& input, HeaderFormat& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "pri_casava1.8")
    {
        output = HEADER_PRI_1_8;
        return true;
    }
    else if (input_lc == "casava1.8")
    {
        output = HEADER_1_8;
        return true;
    }
    else if (input_lc == "unknown")
    {
        output = HEADER_UNKNOWN;
        return true;
    }
    else
        return false;
}

template<>
bool from_string<QualityEncode>(const std::string& input, QualityEncode& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "sanger")
    {
        output = ENCODE_SANGER;
        return true;
    }
    else if (input_lc == "solexa")
    {
        output = ENCODE_SOLEXA;
        return true;
    }
    else if (input_lc == "illumina")
    {
        output = ENCODE_ILLUMINA;
        return true;
    }
    else if (input_lc == "casava1.8")
    {
        output = ENCODE_CASAVA_1_8;
        return true;
    }
    else if (input_lc == "unknown")
    {
        output = ENCODE_UNKNOWN;
        return true;
    }
    else
        return false;
}

template<>
bool from_string<CompressFormat>(const std::string& input, CompressFormat& output)
{
    string input_lc = _prepare_input_(input);

    if (input_lc == "plain")
    {
        output = COMPRESS_PLAIN;
        return true;
    }
    else if (input_lc == "gzip" || input_lc == "gz")
    {
        output = COMPRESS_GZIP;
        return true;
    }
    else if (input_lc == "lzma" || input_lc == "xz")
    {
        output = COMPRESS_LZMA;
        return true;
    }
    else if (input_lc == "bz2" || input_lc == "bzip2")
    {
        output = COMPRESS_BZIP2;
        return true;
    }
    else if (input_lc == "unknown")
    {
        output = COMPRESS_UNKNOWN;
        return true;
    }
    else
        return false;
}

// stringify HTIO enum types
template<>
std::string to_string<Strand>(Strand arg)
{
    switch (arg)
    {
    case STRAND_UNKNOWN:
        return "unknown";
    case STRAND_FWD:
        return "forward";
    case STRAND_REV:
        return "reverse";
    default:
        abort();
    }
}

template<>
std::string to_string<SeqFormat>(SeqFormat arg)
{
    switch (arg)
    {
    case FORMAT_UNKNOWN:
        return "unknown";
    case FORMAT_FASTQ:
        return "fastq";
    case FORMAT_FASTA:
        return "fasta";
    default:
        abort();
    }
}

template<>
std::string to_string<HeaderFormat>(HeaderFormat arg)
{
    switch (arg)
    {
    case HEADER_UNKNOWN:
        return "unknown";
    case HEADER_PRI_1_8:
        return "pri_casava1.8";
    case HEADER_1_8:
        return "casava1.8";
    default:
        abort();
    }
}

template<>
std::string to_string<QualityEncode>(QualityEncode arg)
{
    switch (arg)
    {
    case ENCODE_UNKNOWN:
        return "unknown";
    case ENCODE_SANGER:
        return "sanger";
    case ENCODE_SOLEXA:
        return "solexa";
    case ENCODE_ILLUMINA:
        return "illumina";
    case ENCODE_CASAVA_1_8:
        return "casava1.8";
    default:
        abort();
    }
}

template<>
std::string to_string<CompressFormat>(CompressFormat arg)
{
    switch (arg)
    {
    case COMPRESS_PLAIN:
        return "plain";
    case COMPRESS_GZIP:
        return "gzip";
    case COMPRESS_LZMA:
        return "lzma";
    case COMPRESS_BZIP2:
        return "bzip2";
    case COMPRESS_UNKNOWN:
        return "unknown";
    default:
        abort();
    }
}


} // namespace htio2
