#!/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.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"


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)

    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):
        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="Access self tables, display values, or set new ones totally on your own risk.")

    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)
