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

"""
This project is licensed under the New BSD License:

Copyright (c) 2012-2013, Pierre-Yves Chibon

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Wageningen University nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
"""

"""
This script computes the distance between two GO terms.
This distance is computed as a combination of the number of edges
between the two terms of the tree weighted by one tens of the level
difference between the two terms.
"""

import argparse
import datetime
import os
import sys

try:
    from pygolib import get_logger, download_go_graph, PyGoLib
    from pygolib import __version__, set_logger
    from pygolib.godistance import GoDistanceCounter
    from pygolib.gsesame import GsesameGO, GsesameGene
    from pygolib.oboio import OboIO
except ImportError:
    from src import get_logger, download_go_graph, PyGoLib
    from src import __version__, set_logger
    from src.godistance import GoDistanceCounter
    from src.gsesame import GsesameGO, GsesameGene
    from src.oboio import OboIO


class GoUtilCli(object):
    """ Command Line Interface class for GOUtil. """

    def __init__(self):
        """ Constructor. """
        self.name = os.path.basename(sys.argv[0])
        self.parser = argparse.ArgumentParser(prog="goutil",
                                              version=__version__)
        self.setup_parser()
        self.subparsers = self.parser.add_subparsers(title='Command')
        # Subparser
        self.set_action_distance()
        self.set_action_download_go()
        self.set_action_gs_genedistance()
        self.set_action_gs_godistance()
        self.set_action_info()
        self.set_action_tree()
        self.set_action_subpart()

        # Let's parse now
        self.args = self.parser.parse_args()

        set_logger(self.args.quiet, self.args.verbose)
        self.log = get_logger()
        self.log.debug(self.args)

    def setup_parser(self):
        """ Set the main arguments. """
        self.parser.add_argument('--verbose', action='store_true',
                                 help='Run with verbose output')
        self.parser.add_argument('-q', '--quiet', action='store_true',
                                 help='Run quietly and only print out errors')

    def action_distance(self):
        """ Inform about how much apparts GO terms are. """
        self.log.debug("Action: Distance between GO terms")
        ontology = self.args.ontology
        if not self.args.terms:
            print 'No GO terms specified'
            return 1
        if not ontology:
            ontology = download_go_graph()
        # Load graph
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)

        # Add a common root element if desired
        if self.args.add_root:
            golib = PyGoLib(terms)
            terms = golib.fix_go_graph()

        # Computes the scores
        gdc = GoDistanceCounter(terms)
        cnt = 0
        terms = self.args.terms.split(',')
        for term1 in terms:
            term1 = term1.strip()
            for term2 in terms[cnt + 1:]:
                term2 = term2.strip()
                if term2:
                    scores = gdc.scores(term1, term2)
                    if scores:
                        self.log.info(
                            "The score between %s and %s is:\n"
                            " - %s branches to go from one to the other\n"
                            " - %s level appart" % (term1,
                            term2, scores[0], scores[1]))
                    else:
                        self.log.info(
                            "The score between %s and %s could "
                            "not be computed" % (
                            term1, term2))
            cnt = cnt + 1

    def action_download_go(self):
        """ Download the latest GO term. """
        self.log.debug("Action: Download GO terms")
        download_go_graph(outputfile=self.args.output,
                          force_dl=self.args.force_dl)

    def action_gs_genedistance(self):
        """ Returns the semantic distance between genes based on their
        GO terms. """
        self.log.debug("Action: G-Sesame semantic distance between genes")
        ontology = self.args.ontology
        if not self.args.gene1_goterms:
            print 'No GO terms specified for the first gene'
            return 1
        if not self.args.gene2_goterms:
            print 'No GO terms specified for the second gene'
            return 1
        if not ontology:
            ontology = download_go_graph()
        # Load graph
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)

        # Add a common root element if desired
        if self.args.add_root:
            golib = PyGoLib(terms)
            terms = golib.fix_go_graph()

        # Computes the scores
        gsgo = GsesameGene(terms)
        gene1_go_terms = self.args.gene1_goterms.split(',')
        gene2_go_terms = self.args.gene2_goterms.split(',')
        score = gsgo.scores(gene1_go_terms, gene2_go_terms)
        if score:
            self.log.info(
                'The score between the two genes based on their GO terms'
                ' is: %s' % (score))
        else:
            self.log.info(
                'The score between the two genes could not be computed '
                'using the go terms %s and %s' % (
                gene1_go_terms, gene2_go_terms))

    def action_gs_godistance(self):
        """ Returns the semantic distance between GO terms. """
        self.log.debug("Action: G-Sesame semantic distance between GO terms")
        ontology = self.args.ontology
        if not self.args.terms:
            print 'No GO terms specified'
            return 1
        if not ontology:
            ontology = download_go_graph()
        # Load graph
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)

        # Add a common root element if desired
        if self.args.add_root:
            golib = PyGoLib(terms)
            terms = golib.fix_go_graph()

        # Computes the scores
        gsgo = GsesameGO(terms)
        cnt = 0
        terms = self.args.terms.split(',')
        for term1 in terms:
            term1 = term1.strip()
            for term2 in terms[cnt + 1:]:
                term2 = term2.strip()
                if term2:
                    scores = gsgo.scores(term1, term2)
                    if scores:
                        self.log.info(
                            'The score between %s and %s is:%s' % (term1,
                            term2, scores))
                    else:
                        self.log.info(
                            'The score between %s and %s '
                            'could not be computed' % (
                            term1, term2))
            cnt = cnt + 1

    def action_info(self):
        """ Show the information known about a GO term. """
        self.log.debug("Action: Info on a GO term")
        ontology = self.args.ontology
        if not self.args.term:
            print 'No GO term specified'
            return 1
        if not ontology:
            ontology = download_go_graph()
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)
        try:
            term = terms[self.args.term]
        except KeyError:
            print 'GO term "%s" was not found in the ontology.' % \
                self.args.term
            return 2
        default = ['id', 'name']
        for key in default:
            print '%s : \t %s' % (key, term[key])
        for key in term.keys():
            if key not in default:
                print '%s : \t %s' % (key, term[key])

    def action_subpart(self):
        """ Extract a subpart of the GO graph. """
        self.log.debug("Action: Subpart the GO graph")
        ontology = self.args.ontology
        if not self.args.term:
            print 'No GO term specified'
            return 3
        if not ontology:
            ontology = download_go_graph()
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)
        golib = PyGoLib(terms)
        subgraph = golib.get_sub_graph(terms, self.args.term)
        print "%s terms found in the subgraph" % \
            len(subgraph.keys())
        if subgraph.keys():
            outputfile = 'subgraph-%s-%s.obo' % (
                self.args.term, datetime.datetime.now().strftime('%Y%m%d'))
            obio.graph = subgraph
            obio.write_down_ontology(outputfile)

    def action_tree(self):
        """ Show the tree of a GO term. """
        self.log.debug("Action: Tree of a GO term")
        ontology = self.args.ontology
        if not self.args.term:
            print 'No GO term specified'
            return 1
        if not ontology:
            ontology = download_go_graph()
        obio = OboIO()
        terms = obio.get_graph(
            ontology, no_check_unique=self.args.no_check_unique)
        golib = PyGoLib(terms)
        try:
            term = terms[self.args.term]
        except KeyError:
            print 'GO term "%s" was not found in the ontology.' % \
                self.args.term
            return 2
        print term['id']
        golib.get_path(term, pred=term['id'], paths=[], verbose=True)

    def set_action_distance(self):
        """ Set up the parser for the score action. """
        go_parser = self.subparsers.add_parser(
            'distance',
            help='Return information on the distance between GO terms',
            description='For a given list of GO terms, information '
            'about the distance between each terms will be '
            'returned. The terms should be sperated by a comma ( , )')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.add_argument(
            'terms',
            default=None,
            help='A comma ( , ) separated list of GO terms.')
        go_parser.add_argument(
            '--no-check-unique',
            default=False,
            action='store_true',
            help='Do not check for duplicate term while loading the '
            'ontology. This will greatly increase the loading time but '
            'will warn you if an identifier is double.')
        go_parser.add_argument(
            '--add-root',
            default=False,
            action='store_true',
            help='Add a root element to link the three main categories '
            'of the gene ontology.')
        go_parser.set_defaults(command=self.action_distance)

    def set_action_download_go(self):
        """ Set up the parser for the downloadgo action. """
        go_parser = self.subparsers.add_parser(
            'downloadgo',
            help='Download the latest Gene Ontology',
            description='This command will download in the current '
            'directory the latest version of the gene ontology as you '
            'can find it on the geneontology.org website.')
        go_parser.add_argument(
            '--output',
            default=None,
            help='Precise the name of the output file in which the'
            'gene ontology will be stored. If not provided the file'
            'will be looking like: geneontology-DATE.obo.')
        go_parser.add_argument(
            '--force-dl',
            default=False,
            action='store_true',
            help='Force the download of the ontology even if there is '
            'already a file with the same name in the current directory '
            'in which case the file will be overwriten.')
        go_parser.set_defaults(command=self.action_download_go)

    def set_action_gs_genedistance(self):
        """ Set up the parser for the gs_genedistance action. """
        go_parser = self.subparsers.add_parser(
            'gs_genedistance',
            help='Calculate the semantic distance between genes using '
            'the G-Sesame algorithm',
            description='This method calculates the semantic distance '
            'between two genes through their GO terms using the G-Sesame '
            'algorithm as published in James Z. Wang, Zhidian Du, Rapeeporn '
            'Payattakool, Philip S. Yu and Chin-Fu Chen, A New Method to '
            'Measure the Semantic Similarity of GO Terms, Bioinformatics, '
            '2007, 23: 1274-1281;')
        go_parser.add_argument(
            'gene1_goterms',
            default=None,
            help='A comma ( , ) separated list of GO terms associated '
            'with the first gene.')
        go_parser.add_argument(
            'gene2_goterms',
            default=None,
            help='A comma ( , ) separated list of GO terms associated '
            'with the second gene.')
        go_parser.add_argument(
            '--check-unique',
            default=False,
            action='store_true',
            help='Check for duplicate term while loading the ontology. '
            'This will greatly increase the loading time but will warn '
            'you if an identifier is double.')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.add_argument(
            '--add-root',
            default=False,
            action='store_true',
            help='Add a root element to link the three main categories '
            'of the gene ontology.')
        go_parser.set_defaults(command=self.action_gs_genedistance)

    def set_action_gs_godistance(self):
        """ Set up the parser for the gs_godistance action. """
        go_parser = self.subparsers.add_parser(
            'gs_godistance',
            help='Calculate the semantic distance of GO terms using the '
            'G-Sesame algorithm',
            description='This method calculates the semantic distance '
            'between two GO terms using the G-Sesame algorithm as '
            'published in James Z. Wang, Zhidian Du, Rapeeporn Payattakool,'
            ' Philip S. Yu and Chin-Fu Chen, A New Method to Measure the'
            ' Semantic Similarity of GO Terms, Bioinformatics, 2007, 23: '
            '1274-1281;')
        go_parser.add_argument(
            'terms',
            default=None,
            help='A comma ( , ) separated list of GO terms.')
        go_parser.add_argument(
            '--check-unique',
            default=False,
            action='store_true',
            help='Check for duplicate term while loading the ontology. '
            'This will greatly increase the loading time but will warn '
            'you if an identifier is double.')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.add_argument(
            '--add-root',
            default=False,
            action='store_true',
            help='Add a root element to link the three main categories '
            'of the gene ontology.')
        go_parser.set_defaults(command=self.action_gs_godistance)

    def set_action_info(self):
        """ Set up the parser for the downloadgo action. """
        go_parser = self.subparsers.add_parser(
            'info',
            help='Return all information known about a given GO terms',
            description='This command will return all the information '
            'known about a given GO term in the provided ontology.')
        go_parser.add_argument(
            'term',
            default=None,
            help='The GO term for which to print the information.')
        go_parser.add_argument(
            '--check-unique',
            default=False,
            action='store_true',
            help='Check for duplicate term while loading the ontology. '
            'This will greatly increase the loading time but will warn '
            'you if an identifier is double.')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.set_defaults(command=self.action_info)

    def set_action_subpart(self):
        """ Set up the parser for the subpart action. """
        go_parser = self.subparsers.add_parser(
            'subpart',
            help='Extract a subpart of the GO graph',
            description='This method will extract all the children '
            'of a given GO terms.')
        go_parser.add_argument(
            'term',
            default=None,
            help='The GO term for which to retrieve all the children.')
        go_parser.add_argument(
            '--check-unique',
            default=False,
            action='store_true',
            help='Check for duplicate term while loading the ontology. '
            'This will greatly increase the loading time but will warn '
            'you if an identifier is double.')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.set_defaults(command=self.action_subpart)

    def set_action_tree(self):
        """ Set up the parser for the tree action. """
        go_parser = self.subparsers.add_parser(
            'tree',
            help='Display the tree of ancester for a given GO term',
            description='For a specified GO term, display its direct '
            'ancesters up to the top of the tree.')
        go_parser.add_argument(
            'term',
            default=None,
            help='The GO term for which to print the tree.')
        go_parser.add_argument(
            '--check-unique',
            default=False,
            action='store_true',
            help='Check for duplicate term while loading the ontology. '
            'This will greatly increase the loading time but will warn '
            'you if an identifier is double.')
        go_parser.add_argument(
            '--ontology',
            default=None,
            help='Name of the ontology file to use. If none is '
            'precised, it will download the one from geneontology '
            'directly and use that one.')
        go_parser.set_defaults(command=self.action_tree)


if __name__ == '__main__':
    CLI = GoUtilCli()
    CLI.args.command()
