#include <htio2/PairedEndIO.h>
#include <htio2/QualityUtil.h>
#include "htio2/Cast.h"
#include "htio2/SeqIO.h"

#define USE_prefix_out
#define USE_se_pe
#define USE_encode
#define USE_header_format
#include <htqc/Options.h>
#include <htqc/App.h>
#include <htqc/MultiSeqFile.h>

using namespace std;
using namespace htqc;

bool OPT_out_pe_itlv;
htio2::Option OPT_out_pe_itlv_ENTRY("out-pe-itlv", '\0', OPTION_GROUP_FORMAT,
                                    &OPT_out_pe_itlv, htio2::Option::FLAG_NONE,
                                    "Output paired-end reads will be stored in interleaved format.");

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

htio2::QualityEncode OPT_out_encode;
htio2::Option OPT_out_encode_ENTRY("out-encode", '\0', OPTION_GROUP_FORMAT,
                                   &OPT_out_encode, htio2::Option::FLAG_NONE,
                                   "Output quality score encode.", "STRING");

htio2::Strand OPT_out_pe_strand1()
{
    return _cast_strand_chr(OPT_out_pe_strand[0]);
}

htio2::Strand OPT_out_pe_strand2()
{
    return _cast_strand_chr(OPT_out_pe_strand[1]);
}

void parse_options(int argc, char** argv)
{
    htio2::OptionParser option_parser;
    option_parser.add_option(OPT_files_in_ENTRY);
    option_parser.add_option(OPT_prefix_out_ENTRY);
    option_parser.add_option(OPT_se_ENTRY);
    option_parser.add_option(OPT_pe_ENTRY);

    option_parser.add_option(OPT_encode_ENTRY);
    option_parser.add_option(OPT_pe_itlv_ENTRY);
    option_parser.add_option(OPT_pe_strand_ENTRY);

    option_parser.add_option(OPT_out_encode_ENTRY);
    option_parser.add_option(OPT_out_pe_itlv_ENTRY);
    option_parser.add_option(OPT_out_pe_strand_ENTRY);

    option_parser.add_option(OPT_quiet_ENTRY);
    option_parser.add_option(OPT_help_ENTRY);
    option_parser.add_option(OPT_version_ENTRY);

    option_parser.parse_options(argc, argv);

    if (OPT_help)
    {
        cout << endl
            << argv[0] << " - convert paired-end reads from interleaved format to file pairs, or reverse"<< endl
            << endl
            << option_parser.format_document();
        exit(EXIT_SUCCESS);
    }
    if (OPT_version) htqc::show_version_and_exit();
}

void validate_options()
{
    ensure_files_in();
    ensure_prefix_out();
    ensure_se_pe();
    ensure_encode();
    ensure_header_format();

    if (OPT_pe)
    {
        if (OPT_out_pe_strand == "unknown")
            OPT_out_pe_strand = _OPT_pe_strand;

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

    if (OPT_out_encode == htio2::ENCODE_UNKNOWN)
        OPT_out_encode = OPT_encode;
}

void show_options()
{
    show_se_pe();
    show_files_in();
    show_encode();
    show_prefix_out();
    cout << "# output encode: " << htio2::to_string(OPT_out_encode) << endl;
    if (OPT_pe)
    {
        cout << "# output strand: " << OPT_out_pe_strand << endl;
        if (OPT_out_pe_itlv)
            cout << "# output is interleaved" << endl;
    }
}

void do_se()
{
    // output file name
    string file_out;
    add_fastq_suffix_se(OPT_prefix_out, OPT_compress, file_out);
    check_output_overwrite(OPT_files_in, file_out);

    // io handles
    htqc::MultiSeqFileSE fh_in(OPT_files_in, OPT_compress);
    htio2::SeqIO::Ptr fh_out(htio2::SeqIO::New(file_out, htio2::WRITE, OPT_compress));

    // traverse sequences
    htio2::FastqSeq seq;
    vector<int16_t> qual;

    size_t n_seq = 0;

    while (fh_in.next_seq(seq))
    {
        n_seq++;
        if (OPT_encode != OPT_out_encode)
        {
            htio2::decode_quality(seq.quality, qual, OPT_encode);
            htio2::encode_quality(qual, seq.quality, OPT_out_encode);
        }
        fh_out->write_seq(seq);
        if (!OPT_quiet && n_seq % LOG_BLOCK == 0)
            cout << "  " << n_seq << " processed" << endl;
    }

    if (!OPT_quiet)
        cout << "  " << n_seq << " processed" << endl;
}

void do_pe()
{
    htqc::MultiSeqFilePE* fh_in;
    htio2::PairedEndIO* fh_out;

    htio2::Strand strand_in1 = OPT_pe_strand1();
    htio2::Strand strand_in2 = OPT_pe_strand2();

    if (OPT_pe_itlv)
    {
        fh_in = new htqc::MultiSeqFilePE(OPT_files_in,
                                         strand_in1, strand_in2,
                                         OPT_compress);
    }
    else
    {
        vector<string> files1;
        vector<string> files2;
        separate_paired_files(OPT_files_in, files1, files2);
        fh_in = new htqc::MultiSeqFilePE(files1, files2,
                                         strand_in1, strand_in2,
                                         OPT_compress);
    }

    if (OPT_out_pe_itlv)
    {
        string file_out;
        add_fastq_suffix_se(OPT_prefix_out, OPT_compress, file_out);
        check_output_overwrite(OPT_files_in, file_out);
        fh_out = new htio2::PairedEndIO(file_out,
                                       htio2::WRITE,
                                       OPT_out_pe_strand1(), OPT_out_pe_strand2(),
                                       OPT_compress);
    }
    else
    {
        string file_out1;
        string file_out2;
        add_fastq_suffix_pe(OPT_prefix_out, OPT_compress, file_out1, file_out2);
        check_output_overwrite(OPT_files_in, file_out1);
        check_output_overwrite(OPT_files_in, file_out2);
        fh_out = new htio2::PairedEndIO(file_out1, file_out2,
                                       htio2::WRITE,
                                       OPT_out_pe_strand1(), OPT_out_pe_strand2(),
                                       OPT_compress);
    }

    htio2::FastqSeq seq1;
    htio2::FastqSeq seq2;
    vector<int16_t> qual1;
    vector<int16_t> qual2;

    size_t n_seq = 0;

    while (fh_in->next_pair(seq1, seq2, strand_in1, strand_in2))
    {
        n_seq++;
        if (OPT_encode != OPT_out_encode)
        {
            htio2::decode_quality(seq1.quality, qual1, OPT_encode);
            htio2::decode_quality(seq2.quality, qual2, OPT_encode);
            htio2::encode_quality(qual1, seq1.quality, OPT_out_encode);
            htio2::encode_quality(qual2, seq2.quality, OPT_out_encode);
        }

        fh_out->write_pair(seq1, seq2, strand_in1, strand_in2);
        if (!OPT_quiet && n_seq % LOG_BLOCK == 0)
            cout << "  " << n_seq << " pairs processed" << endl;
    }

    if (!OPT_quiet)
        cout << "  " << n_seq << " pairs processed" << endl;

    delete fh_in;
    delete fh_out;
}

void execute()
{
    if (OPT_se)
        do_se();
    else if (OPT_pe)
        do_pe();
    else
        throw runtime_error("neither single-end nor paired-end");
}

