#!/usr/bin/env python
# -*- coding: utf-8

import sys
import argparse

import anvio
import anvio.db as db
import anvio.tables as t
import anvio.utils as utils
import anvio.dbops as dbops
import anvio.hmmops as hmmops
import anvio.terminal as terminal
import anvio.filesnpaths as filesnpaths

from anvio.errors import ConfigError


__author__ = "Developers of anvi'o (see AUTHORS.txt)"
__copyright__ = "Copyleft 2015-2018, the Meren Lab (http://merenlab.org/)"
__license__ = "GPL 3.0"
__version__ = anvio.__version__
__maintainer__ = "A. Murat Eren"
__email__ = "a.murat.eren@gmail.com"
__description__ = "Access self tables, display values, or set new ones totally on your own risk."


class DatabaseInfo:
    """A class to get information about an anvi'o database, and edit some of them"""
    def __init__(self, args, quiet=False, run=terminal.Run(), progress=terminal.Progress()):
        self.args = args
        self.run = run
        self.progress = progress

        A = lambda x: args.__dict__[x] if x in args.__dict__ else None
        self.db_path =A("input")
        self.self_key = A("self_key")
        self.self_value = A("self_value")
        self.just_do_it = A("just_do_it")

        filesnpaths.is_file_exists(self.db_path)

        self.get_db_self()

        if self.self_key or self.self_value:
            if (not self.self_key) or (not self.self_value):
                raise ConfigError("You must use `--self-key` and `--self-value` together. You can't "
                                  "just use one of them.")

            self.set_key_value()
            self.get_db_self()

        if not quiet:
            run.warning(None, "DB Info (no touch)", lc="green")
            run.info("Database Path", self.db_path)
            if "description" in self.db_self:
                desc = self.db_self["description"]["value"]
                if len(desc) > 30:
                    run.info("Description", "[Found, and it is %d characters long]" % len(desc))
                elif not desc:
                    run.info("Description", "[Not found, but it's OK]", mc="red")
                else:
                    run.info("Description", desc)
            run.info("Type", self.db_type)
            run.info("Version", self.db_version, nl_after=1)

            run.warning(None, "DB Info (no touch also)", lc="green")
            for key in [k for k in self.db_self if k not in ["version", "db_type", "description"]]:
                if key == self.self_key or (self.self_key == 'sample_id' and key == 'samples'):
                    run.info('%s [JUST SET]' % key, self.db_self[key]["value"], mc='green', lc='green')
                else:
                    run.info(key, self.db_self[key]["value"])

            run.info_single("Please remember that it is never a good idea to change these values. But in "
                            "some cases it may be absolutely necessary to update something here, and a "
                            "programmer may ask you to run this program and do it. But even then, you "
                            "should be very skeptical. For instance, if the programmer asks you to change "
                            "the values for 'num_contigs', or 'kmer_size', it would mean that they likely "
                            "hate you and want you to fail. So please be extremely careful changing any "
                            "of these values.", mc='red', nl_before=1)

        # It wouldn't hurt to print out some DB specific information?
        if self.db_type == 'contigs':
            # try-excepting here since this program is supposed to be able to run even on
            # broken databases. so if the following code cause issues because the DB is broken
            # this program shouldn't crash.
            try:
                dbops.ContigsDatabase(self.db_path).list_gene_caller_sources()
                hmmops.SequencesForHMMHits(self.db_path).list_available_hmm_sources()
            except:
                pass


    def get_db_self(self):
        try:
            database = db.DB(self.db_path, None, ignore_version=True)

            self.db_type = database.get_meta_value("db_type")
            self.db_version = int(database.get_meta_value("version"))
            self.db_self = database.get_table_as_dict("self")

            database.disconnect()
        except:
            raise ConfigError("Are you sure '%s' is a database file? Because, you know, probably "
                              "it is not at all :(" % self.db_path)


    def set_key_value(self):
        filesnpaths.is_output_file_writable(self.db_path)
        try:
            database = db.DB(self.db_path, None, ignore_version=True)
            db_type = database.get_meta_value("db_type")
            db_self = database.get_table_as_dict("self")
            database.disconnect()
        except:
            raise ConfigError("Are you sure '%s' is a database file? Because, you know, probably "
                              "it is not at all :(" % self.db_path)


        if self.self_key in ["version", "db_type", "description", "samples"]:
            raise ConfigError("Well. This is awkward. The key '%s' is one of the 'no touch keys', so "
                              "you can't change them with this program. That said, you are an adult and "
                              "anvi'o can't tell you what to do. If you want to be a rebel, feel free to "
                              "use the sqlite from the command line to create your own Frankenstein." % self.self_key)

        # make sure we did everything we can to avoid user error
        self.self_key = self.self_key.replace(' ', '_').strip()

        if self.self_key in db_self and self.self_key == 'sample_id':
            if db_self['db_type']['value'] != 'profile' or int(db_self['blank']['value']) or int(db_self['merged']['value']):
                raise ConfigError("You can change the self-key `sample_id` only in single and non-blank anvi'o "
                                  "profile databases :/")

            if not filesnpaths.is_file_exists(dbops.get_auxiliary_data_path_for_profile_db(self.db_path), dont_raise=True):
                raise ConfigError("You must have the auxiliary database in its proper location if you wish to "
                                  "change the key `sample_id` in a single profile database. If you are tired of "
                                  "anvi'o telling you what you can or cannot do, feel free to tell it to go fly a kite, "
                                  "and open an sqlite terminal to change anything the way you want.")

            utils.check_sample_id(self.self_value)

            self.run.warning("You are trying to change a special key, `sample_id`. This is a special one,\
                              because changing it will require a bunch of other things to change as well\
                              both in other tables in this profile database, and in the auxiliary database\
                              associated with it.")

        if self.self_key in db_self:
            self.run.warning("The content of the key '%s' in the self table of your %s database,\
                              which currently has a value of '%s', will be replaced with '%s'." % \
                                    (self.self_key, db_type, db_self[self.self_key]['value'], self.self_value))
        else:
            self.run.warning("You are about to set a new key, '%s', in the self table of your %s "
                             "database which will hold the value '%s'." % \
                                    (self.self_key, db_type, self.self_value))

        if not self.just_do_it:
            self.run.info_single('If you like what you see up there, please use the `--just-do-it` '
                                 'flag to make it happen.')
            sys.exit(0)

        database = db.DB(self.db_path, None, ignore_version=True)
        database.remove_meta_key_value_pair(self.self_key)
        database.set_meta_value(self.self_key, self.self_value)
        database.disconnect()

        if self.self_key == 'sample_id':
            database = db.DB(self.db_path, None, ignore_version=True)
            current_sample_name = database.get_meta_value("samples")

            self.progress.new("Changing '%s' to '%s'" % (current_sample_name, self.self_value), progress_total_items=5)
            self.progress.increment()
            self.progress.update('In self ...')
            database.remove_meta_key_value_pair('samples')
            database.set_meta_value('samples', self.self_value)

            self.progress.increment()
            self.progress.update('In layer additional table')
            database._exec("""UPDATE %s SET "item_name" = ? WHERE "item_name" == ?""" % t.layer_additional_data_table_name, (self.self_value, current_sample_name))

            self.progress.increment()
            self.progress.update('In variable nucleotides table')
            database._exec("""UPDATE %s SET "sample_id" = ? WHERE "sample_id" == ?""" % t.variable_nts_table_name, (self.self_value, current_sample_name))

            self.progress.increment()
            self.progress.update('In variable codons table')
            database._exec("""UPDATE %s SET "sample_id" = ? WHERE "sample_id" == ?""" % t.variable_codons_table_name, (self.self_value, current_sample_name))
            database.disconnect()

            self.progress.increment()
            self.progress.update('In auxiliary data table')
            database = db.DB(dbops.get_auxiliary_data_path_for_profile_db(self.db_path), None, ignore_version=True)
            database._exec("""UPDATE %s SET "sample_name" = ? WHERE "sample_name" == ?""" % t.split_coverages_table_name, (self.self_value, current_sample_name))
            database.disconnect()

            self.progress.end()


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__description__)

    groupA = parser.add_argument_group('Input', "The database path you wish to access.")
    groupA.add_argument("input", metavar = "DATABASE_PATH", nargs=None, help = "An anvi'o database for pan, profile, contigs, or auxiliary data")

    groupB = parser.add_argument_group('Very dangerous zone', "For power users with extreme self-control and maturity.")
    groupB.add_argument(*anvio.A('self-key'), **anvio.K('self-key'))
    groupB.add_argument(*anvio.A('self-value'), **anvio.K('self-value'))
    groupB.add_argument(*anvio.A('just-do-it'), **anvio.K('just-do-it'))
    args, unknown = parser.parse_known_args()

    try:
        DatabaseInfo(args)
    except ConfigError as e:
        print(e)
        sys.exit(-1)
