import unittest.suite
import subprocess
import tempfile
import logging
import shutil
import json
import os.path as op
import gzip
import os
import re
import sys

from xmlbuilder import XMLBuilder
import pysam
import xml.etree.ElementTree as ET

from pbcore.io import AlignmentSet
from pbcore.io.FastaIO import FastaReader
from pbcore.io.FastqIO import FastqReader
from pbcommand.models import FileTypes

from pbsmrtpipe.testkit import butler as B
from pbsmrtpipe.testkit.service_runner import (get_entrypoints,
    entrypoints_dicts, pipeline_id_from_testkit_cfg, job_id_from_testkit_cfg)

log = logging.getLogger(__name__)

__all__ = ['convert_suite_and_result_to_xunit']


def iterate_gzipped_bam2fastx_outputs(file_type, file_name, is_barcoded=False):
    """
    Iterate over individual FASTX outputs from bam2fasta/bam2fastq, which may
    be either single files or a tar archive of barcoded files.
    """
    if not is_barcoded:
        yield gzip.open(file_name)
    else:
        _cwd = os.getcwd()
        tmpdir = tempfile.mkdtemp()
        try:
            os.chdir(tmpdir)
            assert subprocess.call(["tar", "zxf", file_name]) == 0
            for file_name in os.listdir(tmpdir):
                assert file_name.endswith(file_type.ext)
                yield open(file_name)
        finally:
            os.chdir(_cwd)
            shutil.rmtree(tmpdir)


class FastxStats(object):

    """ Generalized Class to get information about Fast[a|q] files
       :param: min_length minimum length of sequence to count
       :param: total_length - quantity of bases used for N50 calculation

       :returns: stats_dictionary with keys: 'num','sum','max','avg','n50','99%'
    """

    def __init__(self, seq_file, file_type=None, is_barcoded=None):

        if file_type is None:
            seq_type = op.splitext(seq_file)[1].lstrip(".")
            file_types = {"fasta": FileTypes.FASTA,
                          "fastq": FileTypes.FASTQ}[seq_type]

        log.info('Initializing FastxStats with:')
        log.info(
            'Input type \'{t}\' and File: {f}'.format(t=file_type, f=seq_file))

        file_readers_d = {"fasta": FastaReader, "fastq": FastqReader}
        self.lengths = []
        for file_obj in iterate_gzipped_bam2fastx_outputs(
                file_type=file_type,
                file_name=seq_file,
                is_barcoded=is_barcoded):
            file_reader = file_readers_d[file_type.ext]
            with file_reader(file_obj) as fastx_in:
                self.lengths.extend([len(rec.sequence) for rec in fastx_in])
        self.lengths.sort()
        self.lengths.reverse()
        self.fasta_file = op.basename(seq_file)

    def get_stats(self):

        log.info('Getting fastx stats')
        total_length = sum(self.lengths)
        contigs_length = []
        length = 0

        stats = {"file": self.fasta_file,
                 "num": len(self.lengths),
                 "sum": sum(self.lengths),
                 "max": max(self.lengths),
                 "avg": int(sum(self.lengths) / float(len(self.lengths))),
                 "n50": 0,
                 "99%": 0}

        length_sum = 0
        for idx, length in enumerate(self.lengths):
            length_sum += length
            if length_sum > total_length / 2:
                stats["n50"] = length
                break

        length_sum = 0
        for idx, length in enumerate(self.lengths):
            length_sum += length
            if length_sum > total_length * 0.99:
                stats["99%"] = idx + 1
                break
        return stats


def analysis_json(job_id, pipeline_id, entrypoints):
    eps = entrypoints_dicts(entrypoints)
    analysis_json = {"entryPoints": eps,
                     "name": "{j}".format(j=job_id),
                     "pipelineId": "{x}".format(x=pipeline_id),
                     "taskOptions": [],
                     "workflowOptions": []}
    js = json.dumps(analysis_json)
    return js


def get_smrttools_changelist(smrt_root):
    """Determine perforce CL from smrt_root
    """
    changelist = None
    smrtsuite_tools_path = os.path.join(
        smrt_root, 'current/bundles/smrttools/install')
    smrttools_standalone_path = os.path.join(smrt_root, 'install')
    if os.path.exists(smrtsuite_tools_path):
        log.info("Checking SMRTSuite install for smrttools CL #")
        installed = os.listdir(smrtsuite_tools_path)[0]
        if installed.split('-')[0] == 'smrttools':
            changelist = installed.split('.')[-1]
        else:
            log.warn(
                'no suitable smrttools installation found. check your SMRT_ROOT: {s}'.format(s=smrt_root))
    elif os.path.exists(smrttools_standalone_path):
        installed = os.listdir(smrttools_standalone_path)[0]
        if installed.split('_')[0] == 'smrtanalysis':
            changelist = installed.split('.')[-1]
            log.info(
                "Not a SMRTSuite installation, looking for smrtanalysis version")
        else:
            log.warn(
                'no suitable smrttools installation found. check your SMRT_ROOT: {s}'.format(s=smrt_root))
    elif smrt_root.endswith(".installdir"):
        fields = smrt_root.split(".")
        changelist = fields[-2]
    log.info("Using SMRT_ROOT: {e}".format(e=smrt_root))
    log.info("Using with smrttools CL: {c}".format(c=changelist))

    return changelist


def was_workflow_successful(job_output):
    successful_str = "Workflow was Successful"
    pbsmrtpipe_log = os.path.join(job_output, 'logs', 'pbsmrtpipe.log')
    state = False

    if os.path.exists(pbsmrtpipe_log):
        with open(pbsmrtpipe_log, 'r') as f:
            for line in f:
                if successful_str in line:
                    state = True
                    return state
    return state


def get_testkit_cfgs(root_job_dir, only_completed=False):
    """Return testkit.cfg file names"""
    log.info('Looking for testkit.cfgs')
    testkit_cfgs = []
    for dir_name, dir_names, file_names in os.walk(root_job_dir):
        if 'testkit.cfg' in file_names:
            testkit_cfg = os.path.join(dir_name, 'testkit.cfg')
            job_output = os.path.join(dir_name, 'job_output')
            was_successful = was_workflow_successful(job_output)

            if only_completed:
                if was_successful:
                    testkit_cfgs.append(testkit_cfg)
            else:
                testkit_cfgs.append(testkit_cfg)

    return testkit_cfgs


def find_discordant_mappings(file_name, max_subread_distance=25000):
    """
    Verify that aligned subreads from the same polymerase read are concordant.
    Written as a generator to facilitate interactive use.
    """
    mapping_dict = {}
    n = 0
    with AlignmentSet(file_name) as ds:
        for alignment in ds:
            read_id = (alignment.movieName, alignment.HoleNumber)
            reference_name = alignment.referenceInfo.FullName
            reference_pos = int(alignment.tStart)  # Comes as a uint
            if read_id not in mapping_dict:
                mapping_dict[read_id] = (reference_name, reference_pos,
                                         alignment.qName)
            else:
                assert reference_name == mapping_dict[read_id][0]
                delta = mapping_dict[read_id][1] - reference_pos
                msg = "non-concordant mappings for {a} and {b}: " +\
                      "delta={d} (= |{t} - {u}|)"
                if abs(delta) > max_subread_distance:
                    yield msg.format(a=mapping_dict[read_id][2],
                                     b=alignment.qName,
                                     d=delta,
                                     t=mapping_dict[read_id][1],
                                     u=alignment.tStart)


def _read_json(json_path):
    with open(json_path, 'rw') as f:
        json_dict = json.load(f)
    return json_dict


def parse_metric_attributes(json_path):
    metric_list = []
    log.info("Gather metric data from {j}".format(j=json_path))
    json_dict = _read_json(json_path)
    if json_dict:
        metrics = json_dict['attributes']
        for m in metrics:
            mid = m['id']
            value = m['value']
            metric_list.append((mid, value))

    return metric_list


def testkit_to_analysis_json(testkit_cfg, output=None):
    entrypoints = get_entrypoints(testkit_cfg)
    pipeline_id = pipeline_id_from_testkit_cfg(testkit_cfg)
    job_id = job_id_from_testkit_cfg(testkit_cfg)
    json_str = analysis_json(job_id, pipeline_id, entrypoints)
    log.info(json_str)
    if output is None:
        output = os.path.join(os.getcwd(), '{j}_analysis.json'.format(j=job_id))
    _write_json(json_str, output)
    log.info("wrote {f}".format(f=output))
    return output


def _write_json(analysis_json, output):
    try:
        with open(output, 'w') as o:
            o.write(analysis_json)
    except Exception as e:
        log.error('Proeu ERROR')
