//     HTQC - a high-throughput sequencing quality control toolkit
//
//     This program is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
//
//     This program is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
//
//     You should have received a copy of the GNU General Public License
//     along with this program.  If not, see <http://www.gnu.org/licenses/>.

#define USE_file_out
#define USE_prefix_out
#define USE_se_pe
#define USE_encode
#define USE_header_format
#define USE_mask
#include "htqc/Options.h"
#include "htio2/Cast.h"
#include "htio2/Version.h"
#include "htio2/FastqIO.h"
#include "htio2/FormatUtil.h"
#include "htio2/HeaderUtil.h"
#include "htio2/QualityUtil.h"

using namespace std;
using namespace htio2;

namespace htqc
{

bool OPT_help;
htio2::Option OPT_help_ENTRY("help", 'h', OPTION_GROUP_MISC,
                             &OPT_help, htio2::Option::FLAG_NONE,
                             "Show help.");

vector<string> OPT_files_in;
htio2::Option OPT_files_in_ENTRY("in", 'i', OPTION_GROUP_IO,
                                 &OPT_files_in, htio2::Option::FLAG_MULTI_KEY, htio2::ValueLimit::Free(),
                                 "Input files.", "FILE1 [FILE2 ...]");

std::string OPT_prefix_out;
htio2::Option OPT_prefix_out_ENTRY("out", 'o', OPTION_GROUP_IO,
                                   &OPT_prefix_out, htio2::Option::FLAG_NONE,
                                   "Output file prefix.", "FILE_PREFIX");

htio2::QualityEncode OPT_encode(htio2::ENCODE_UNKNOWN);
htio2::Option OPT_encode_ENTRY("encode", '\0', OPTION_GROUP_FORMAT,
                               &OPT_encode, htio2::Option::FLAG_NONE,
                               "Quality score encode.\n"
                               "sanger: 0 - 93 using ASCII 33 - 126\n"
                               "solexa: -5 - 62 using ASCII 59 - 126\n"
                               "illumina: 0 - 62 using ASCII 64 - 126\n"
                               "casava1.8: 0 - 62 using ASCII 33 - 95; 0 1 not used; values >40 rarely used",
                               "STRING");

htio2::HeaderFormat OPT_header_format(htio2::HEADER_1_8);
htio2::Option OPT_header_format_ENTRY("header-format", '\0', OPTION_GROUP_FORMAT,
                                      &OPT_header_format, htio2::Option::FLAG_NONE,
                                      "FASTQ header format.\n"
                                      "casava1.8: produced by Illumina CASAVA version 1.8\n"
                                      "pri_casava1.8: \tproduced by Illumina CASAVA version 1.7 or lower",
                                      "STRING");

bool OPT_header_sra(false);
htio2::Option OPT_header_sra_ENTRY("header-sra", '\0', OPTION_GROUP_FORMAT,
                                   &OPT_header_sra, htio2::Option::FLAG_NONE,
                                   "Whether FASTQ header has NCBI SRA addon.");

bool OPT_mask(false);
htio2::Option OPT_mask_ENTRY("quality-mask", '\0', OPTION_GROUP_FORMAT,
                             &OPT_mask, htio2::Option::FLAG_NONE,
                             "Treat quality score 2 as a mask.");

bool OPT_version(false);
htio2::Option OPT_version_ENTRY("version", 'v', OPTION_GROUP_MISC,
                                &OPT_version, htio2::Option::FLAG_NONE,
                                "Show version.");

bool OPT_quiet(false);
htio2::Option OPT_quiet_ENTRY("quiet", 'q', OPTION_GROUP_MISC,
                              &OPT_quiet, htio2::Option::FLAG_NONE,
                              "Do not print information during program run.");

bool OPT_se(false);
htio2::Option OPT_se_ENTRY("se", 'S', OPTION_GROUP_FORMAT,
                           &OPT_se, htio2::Option::FLAG_NONE,
                           "Single-end reads.");
bool OPT_pe(false);
htio2::Option OPT_pe_ENTRY("pe", 'P', OPTION_GROUP_FORMAT,
                           &OPT_pe, htio2::Option::FLAG_NONE,
                           "Paired-end reads.");

bool OPT_pe_itlv(false);
htio2::Option OPT_pe_itlv_ENTRY("pe-itlv", '\0', OPTION_GROUP_FORMAT,
                                &OPT_pe_itlv, htio2::Option::FLAG_NONE,
                                "Paired-end reads are stored in interleaved format.");

std::string _OPT_pe_strand("FR");
htio2::Option OPT_pe_strand_ENTRY("pe-strand", '\0', OPTION_GROUP_FORMAT,
                                  &_OPT_pe_strand, htio2::Option::FLAG_NONE,
                                  "The strand of paired-end reads. \"F\" for forward, \"R\" for reverse. 1st character for read 1, 2nd character for read 2.",
                                  "FF|FR|RF|RR");

htio2::CompressFormat OPT_compress = htio2::COMPRESS_UNKNOWN;

void show_files_in()
{
    cout << "# input files:" << endl;
    if (OPT_se)
    {
        for (size_t i = 0; i < OPT_files_in.size(); i++)
            cout << "#   " << OPT_files_in[i] << endl;
    }
    else if (OPT_pe)
    {
        if (OPT_pe_itlv)
        {
            for (size_t i = 0; i < OPT_files_in.size(); i++)
                cout << "#   " << OPT_files_in[i] << endl;
        }
        else
        {
            if (OPT_files_in.size() % 2)
                throw runtime_error("odd number of input files");
            size_t hf_sz = OPT_files_in.size() / 2;
            for (size_t i = 0; i < hf_sz; i++)
                cout << "#   " << OPT_files_in[i] << " and " << OPT_files_in[i + hf_sz] << endl;
        }
    }
}

void ensure_files_in()
{
    if (!OPT_files_in.size())
    {
        cerr << "ERROR: input files not specified" << endl;
        exit(EXIT_FAILURE);
    }
}

void show_prefix_out()
{
    cout << "# output prefix: " << OPT_prefix_out << endl;
}

void ensure_prefix_out()
{
    tidy_prefix_out(OPT_prefix_out);
    if (!OPT_prefix_out.length())
    {
        cerr << "ERROR: output file prefix not specified" << endl;
        exit(EXIT_FAILURE);
    }
}

htio2::Strand _cast_strand_chr(char value)
{
    switch (value)
    {
    case 'F':
    case 'f':
        return htio2::STRAND_FWD;
    case 'R':
    case 'r':
        return htio2::STRAND_REV;
    default:
        cerr << "ERROR: invalid strand character representation: " << value << ". Only F/R/f/r allowed." << endl;
        exit(EXIT_FAILURE);
    }
}

htio2::Strand OPT_pe_strand1()
{
    return _cast_strand_chr(_OPT_pe_strand[0]);
}

htio2::Strand OPT_pe_strand2()
{
    return _cast_strand_chr(_OPT_pe_strand[1]);
}

void show_se_pe()
{
    if (OPT_se)
    {
        cout << "# single-end" << endl;
    }
    else if (OPT_pe)
    {
        cout << "# paired-end" << endl;
        if (OPT_pe_itlv)
            cout << "#   interleave file" << endl;
        else
            cout << "#   file pair" << endl;
        cout << "#   strand: " << _OPT_pe_strand << endl;
    }
    else
    {
        cerr << "ERROR: neither single nor paired" << endl;
        exit(EXIT_FAILURE);
    }
}

void ensure_se_pe()
{
    if ((OPT_se && OPT_pe) ||
        !(OPT_se || OPT_pe))
    {
        cerr << "ERROR: single-end (--se, -S) or paired-end (--pe, -P)?" << endl;
        exit(EXIT_FAILURE);
    }

    if (OPT_pe_itlv && !OPT_pe)
    {
        cerr << "WARNING: implicitly enabled paired-end mode, because interleave mode was selected" << endl;
        OPT_pe = true;
    }

    if (_OPT_pe_strand.length() != 2
        || !check_strand_chr(_OPT_pe_strand[0])
        || !check_strand_chr(_OPT_pe_strand[1]))
    {
        cerr << "ERROR: invalid paired-end strand: \"" << _OPT_pe_strand << "\". Valid values: FF/FR/RF/RR (lower case allowed)." << endl;
        exit(EXIT_FAILURE);
    }
}

void show_encode()
{
    cout << "# quality encode: " << htio2::to_string(OPT_encode) << endl;
}

void ensure_encode()
{
    if (OPT_encode == htio2::ENCODE_UNKNOWN)
    {
        if (!OPT_quiet) cout << "guess encode from front " << NUM_GUESS << " sequences of file " << OPT_files_in[0] << endl;

        FastqIO fh(OPT_files_in[0], htio2::READ);
        OPT_encode = guess_quality_encode(fh, NUM_GUESS);
        if (OPT_encode == htio2::ENCODE_UNKNOWN)
        {
            cerr << "ERROR: failed to guess quality encode from input file " << OPT_files_in[0] << endl;
            exit(EXIT_FAILURE);
        }

        if (!OPT_quiet)
            cout << "encode is: " << htio2::to_string(OPT_encode) << endl;
    }
}

void show_header_format()
{
    cout << "# header format: " << htio2::to_string(OPT_header_format) << endl;
}

void ensure_header_format()
{
    if (OPT_header_format == htio2::HEADER_UNKNOWN)
    {
        if (!OPT_quiet) cout << "guess header format from front " << NUM_GUESS << " sequences of file " << OPT_files_in[0] << endl;
        htio2::FastqIO fh(OPT_files_in[0], htio2::READ);
        htio2::guess_header_format(fh, NUM_GUESS, OPT_header_format, OPT_header_sra);
        if (OPT_header_format == htio2::HEADER_UNKNOWN)
        {
            cerr << "ERROR: failed to guess header format from input file " << OPT_files_in[0] << endl;
            exit(EXIT_FAILURE);
        }
        if (!OPT_quiet)
        {
            cout << "header format is " << to_string(OPT_header_format);
            if (OPT_header_sra) cout << ", with SRA" << endl;
            else cout << ", without SRA" << endl;
        }
    }
}

void show_mask()
{
    cout << "# quality has mask: " << bool_to_string(OPT_mask) << endl;
}

htio2::CompressFormat guess_compress_format_by_files_name(const std::vector<std::string>& files)
{
    htio2::CompressFormat comp = htio2::COMPRESS_UNKNOWN;
    string prev_file;
    for (size_t i=0; i<files.size(); i++)
    {
        htio2::CompressFormat curr_comp = guess_compress_by_name(files[i]);

        if (curr_comp == htio2::COMPRESS_UNKNOWN)
            continue;

        if (comp == htio2::COMPRESS_UNKNOWN)
        {
            comp = curr_comp;
            prev_file = files[i];
        }
        else
        {
            if (comp != curr_comp)
            {
                cerr << "ERROR: conflict compress format by \"" << prev_file << "\" and \"" << files[i] << "\"" << endl;
                exit(EXIT_FAILURE);
            }
        }
    }

    return comp;
}

void tidy_prefix_out(std::string & prefix)
{
    remove_suffix(prefix, ".gz");
    remove_suffix(prefix, ".fq");
    remove_suffix(prefix, ".fastq");
}

void show_version_and_exit()
{
    cout << htio2::get_version_string() << endl;
    exit(EXIT_SUCCESS);
}

void remove_suffix(std::string& name, const std::string & suffix)
{
    size_t suffix_pos = name.rfind(suffix);
    if (suffix_pos != string::npos)
        name.erase(suffix_pos);
}

} // namespace htqc
