import sys
import os
import subprocess
import json
import straight.plugin
from tempfile import TemporaryFile
from os import path
from core import log
import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    from Bio import SearchIO
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
from helperlibs.wrappers.io import TemporaryDirectory
from sklearn.externals import joblib
from time import gmtime, strftime


def get_version():
    return "0.0.0alpha"


def load_plugins(module):
    ""
    plugins = list(straight.plugin.load("modules.%s" % module))
    plugins.sort(cmp=lambda x, y: cmp(x.priority, y.priority))
    return plugins
available_algorithms = {
    "classification": [{"name": plugin.name, "description": plugin.description} for plugin in load_plugins("classification")],
    "regression": [{"name": plugin.name, "description": plugin.description} for plugin in load_plugins("regression")]
}
available_features = [{"name": plugin.name, "description": plugin.description} for plugin in load_plugins("feature_extraction")]


def get_algo_type(algo):
    ""
    result = ""
    for plugin in available_algorithms["classification"]:
        if algo == plugin["name"]:
            result = "classification"
            break
    for plugin in available_algorithms["regression"]:
        if algo == plugin["name"]:
            result = "regression"
            break
    return result


def check_unsupported_features(feature_list):
    ""
    mapped_features = []
    for feature in feature_list:
        for plugin in available_features:
            if feature == plugin["name"]:
                mapped_features.append(feature)
    unmapped_features = list(set(feature_list) - set(mapped_features))
    return unmapped_features


def save_bgcjson(bgc, output_folder):
    ""
    save_folder = path.join(output_folder, "bgcjson")
    if not path.exists(save_folder):
        if not path.isdir(save_folder):
            os.makedirs(save_folder)
        else:
            log.error("Failed to save bgcjson, folder '%s' is unwritable" % save_folder)
            sys.exit(1)
    with open(path.join(save_folder, "%s.bgcjson" % bgc["id"]), "w") as bgcjson:
        bgcjson.write(json.dumps(bgc, indent=4, separators=(',', ': ')))


def save_result_model(classifier, metadata, output_folder):
    ""
    result_folder = path.join(output_folder, "results")
    if not path.exists(result_folder):
        if not path.isdir(result_folder):
            os.makedirs(result_folder)
        else:
            log.error("Failed to create result folder")
            sys.exit(1)
    folder_name = "model-%s-%s" % (metadata["algorithm"]["name"], strftime("%Y%m%d-%H%M%S", gmtime()))
    model_folder = path.join(result_folder, folder_name)
    if not path.exists(model_folder):
        os.makedirs(model_folder)
        joblib.dump(classifier, path.join(model_folder, "classifier.pkl"))
        with open(path.join(model_folder, "metadata.txt"), "w") as metafile:
            metafile.write(json.dumps(metadata, indent=4, separators=(',', ': ')))
    else:
        log.error("Cannot write into '%s', folder exist." % (model_folder))
        sys.exit(1)
    return folder_name


def save_result_testing(folder_name, rows, columns, X, y, predicted, output_folder):
    ""
    result_folder = path.join(output_folder, "results")
    if not path.exists(result_folder):
        if not path.isdir(result_folder):
            os.makedirs(result_folder)
        else:
            log.error("Failed to create result folder")
            sys.exit(1)
    save_folder = path.join(result_folder, folder_name)
    if not path.exists(save_folder):
        os.makedirs(save_folder)
        with open(path.join(save_folder, "result.txt"), "w") as resfile:
            log.info("%s scores:" % folder_name)
            for key, value in predicted[0].iteritems():
                log.info("%s=%s" % (key, str(value)))
                resfile.write("%s=%s\n" % (key, str(value)))
            resfile.write("Predictions:\n")
            resfile.write("row_name\t%s\texpected\tpredicted\n" % ("\t".join(columns)))
            for i, fname in enumerate(rows):
                resfile.write("%s\t%s\t%s\t%s\n" % (fname, "\t".join(["%.8f" % val for val in X[i]]), str(y[i]), str(predicted[1][i])))
    else:
        log.error("Cannot write into '%s', folder exist." % (model_folder))
        sys.exit(1)
    return folder_name


def create_feature_folder(input_files, output_folder, overwrite=True):
    ""
    save_folder = path.join(output_folder, "features")
    save_file = path.join(save_folder, "index.txt")
    if not path.exists(save_folder):
        if not path.isdir(save_folder):
            os.makedirs(save_folder)
        else:
            log.error("Failed to create feature folder, folder '%s' is unwritable" % save_folder)
            sys.exit(1)
    if (not path.exists(save_file)) or overwrite:
        with open(save_file, "w") as f:
            for i, val in enumerate(input_files):
                f.write("%d:%s\n" % (i, get_bgc_name(val)))
    return save_folder


def get_bgc_name(file_path):
    ""
    return path.splitext(path.basename(file_path))[0]


# ported from https://github.com/antismash/antismash
# pylint: disable=redefined-builtin
def execute(commands, input=None):
    "Execute commands in a system-independent manner"

    if input is not None:
        stdin_redir = subprocess.PIPE
    else:
        stdin_redir = None

    try:
        proc = subprocess.Popen(commands, stdin=stdin_redir,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)
        out, err = proc.communicate(input=input)
        retcode = proc.returncode
        return out, err, retcode
    except OSError, e:
        logging.debug("%r %r returned %r", commands, input[:40] if input is not None else None, e)
        raise
# pylint: enable=redefined-builtin


def run_hmmscan(query_sequence, opts=None):
    ""
    # TODO: check if hmmscan available
    # check if Pfam-A.hmm exists
    pfam_folder = path.abspath(path.join(path.realpath(__file__), path.pardir, path.pardir, "resources", "pfamdb"))
    if not path.exists(pfam_folder):
        os.makedirs(pfam_folder)
    else:
        if not path.isdir(pfam_folder):
            log.error("Failed to do hmmscan, folder '%s' is unwritable" % pfam_folder)
            sys.exit(1)
    if not path.exists(path.join(pfam_folder, "Pfam-A.hmm.h3m")):
        log.error("Pfam-A database not downloaded, please run download_resources.py first")
        sys.exit(1)

    results = []
    with TemporaryDirectory() as temp_dir:
        domtblout = path.abspath(path.join(temp_dir, "domtblout"))
        textout = path.abspath(path.join(temp_dir, "textout"))
        command = ["hmmscan", "--cut_tc", "--domtblout", domtblout, "-o", textout]
        temp_fasta = path.abspath(path.join(temp_dir, "temp_fasta"))
        with open(temp_fasta, "w") as tf:
            tf.write(query_sequence)
        if opts is not None:
            command.extend(opts)
        command.extend([path.join(pfam_folder, "Pfam-A.hmm"), temp_fasta])
        try:
            out, err, retcode = execute(command)
        except OSError:
            return []
        if retcode != 0:
            log.error('Hmmscan returned %d: %r while scanning %r' % (retcode, err, query_sequence))
            sys.exit(1)
        results = {
            "data": list(SearchIO.parse(textout, 'hmmer3-text')),
            "accessions": {},
            "lengths": {},
        }
        for scan in list(SearchIO.parse(domtblout, 'hmmscan3-domtab')):
            for hit in scan.hits:
                if hit.id not in results["accessions"]:
                    results["accessions"][hit.id] = hit.accession
                if hit.id not in results["lengths"]:
                    results["lengths"][hit.id] = hit.seq_len
    return results


def align_hsp_to_model(hsp, model_length): # this works only for hmmscan model (cds as query)
    pad_left = ""
    pad_right = ""
    for i in xrange(0, hsp.hit_start):
        pad_left += "."
    for i in xrange(hsp.hit_end, model_length):
        pad_right += "."
    if hsp.hit_strand != hsp.query_strand:
        pad_left, pad_right = pad_right, pad_left
    return "%s%s%s" % (pad_left, str(hsp.hit.seq).replace(".", ""), pad_right)


# -*- coding: utf-8 -*-
# Print iterations progress
def print_progress(iteration, total, prefix='', suffix='', decimals=1, bar_length=50):
    str_format = "{0:." + str(decimals) + "f}"
    percents = str_format.format(100 * (iteration / float(total)))
    filled_length = int(round(bar_length * iteration / float(total)))
    bar = '*' * filled_length + '-' * (bar_length - filled_length)
    sys.stdout.write('\r%s %s%s (%i/%i) %s' % (prefix, percents, '%', iteration, total, suffix)),
    if iteration == total:
        sys.stdout.write('\n')
    sys.stdout.flush()
