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

"""
    g-octave
    ~~~~~~~~

    Main script of g-Octave.

    :copyright: (c) 2009-2010 by Rafael Goncalves Martins
    :license: GPL-2, see LICENSE for more details.
"""

from __future__ import print_function

has_fetch = True

__issue_tracker = 'http://www.g-octave.org/'

import sys

# This block ensures that ^C interrupts are handled quietly.
# Code snippet from Portage
try:
    import signal

    def exithandler(signum,frame):
        signal.signal(signal.SIGINT, signal.SIG_IGN)
        signal.signal(signal.SIGTERM, signal.SIG_IGN)
        sys.exit(1)

    signal.signal(signal.SIGINT, exithandler)
    signal.signal(signal.SIGTERM, exithandler)

except KeyboardInterrupt:
    sys.exit(1)


import getpass
import os
import optparse
import portage
import subprocess

out = portage.output.EOutput()

current_dir = os.path.dirname(os.path.realpath(__file__))
if os.path.exists(os.path.join(current_dir, '..', 'g_octave')):
    sys.path.insert(0, os.path.join(current_dir, '..'))

from g_octave.log import Log
log = Log('g-octave')

log.info('Initializing...')
import g_octave

def main():

    parser = optparse.OptionParser(
        usage = '%prog [options] <package_name | package_name-version>',
        version = '%prog ' + g_octave.__version__,
        description = g_octave.__description__
    )

    parser.add_option(
        '-l', '--list',
        action = 'store_true',
        dest = 'list',
        default = False,
        help = 'show a list of packages available to install (separed by categories) and exit'
    )

    parser.add_option(
        '-i', '--info',
        action = 'store_true',
        dest = 'info',
        default = False,
        help = 'show a description of the required package and exit'
    )

    parser.add_option(
        '-p', '--pretend',
        action = 'store_true',
        dest = 'pretend',
        default = False,
        help = 'don\'t (un)merge packages, only create ebuilds and solve the dependencies'
    )

    parser.add_option(
        '-a', '--ask',
        action = 'store_true',
        dest = 'ask',
        default = False,
        help = 'ask to confirmation before perform (un)merges'
    )

    parser.add_option(
        '-v', '--verbose',
        action = 'store_true',
        dest = 'verbose',
        default = False,
        help = 'package manager\'s makes a lot of noise.'
    )

    parser.add_option(
        '-1', '--oneshot',
        action = 'store_true',
        dest = 'oneshot',
        default = False,
        help = 'do not add the packages to the world file for later updating.'
    )

    parser.add_option(
        '-u', '--update',
        action = 'store_true',
        dest = 'update',
        default = False,
        help = 'try to update a package or all the installed packages'
    )

    parser.add_option(
        '-s', '--search',
        action = 'store_true',
        dest = 'search',
        default = False,
        help = 'search for packages with some term on the name (regular expressions allowed)'
    )

    parser.add_option(
        '-C', '--unmerge',
        action = 'store_true',
        dest = 'unmerge',
        default = False,
        help = 'try to unmerge a package instead of merge'
    )

    parser.add_option(
        '--scm',
        action = 'store_true',
        dest = 'scm',
        default = False,
        help = 'enable the installation of the current live version of a package, if disabled on the configuration file'
    )

    parser.add_option(
        '--no-scm',
        action = 'store_true',
        dest = 'no_scm',
        default = False,
        help = 'disable the installation of the current live version of a package, if enabled on the configuration file'
    )

    parser.add_option(
        '--force',
        action = 'store_true',
        dest = 'force',
        default = False,
        help = 'forces the recreation of the ebuilds'
    )

    parser.add_option(
        '--force-all',
        action = 'store_true',
        dest = 'force_all',
        default = False,
        help = 'forces the recreation of the overlay and of the ebuilds'
    )

    parser.add_option(
        '--no-colors',
        action = 'store_false',
        dest = 'colors',
        default = True,
        help = 'don\'t use colors on the CLI'
    )

    parser.add_option(
        '--sync',
        action = 'store_true',
        dest = 'sync',
        default = False,
        help = 'search for updates of the package database, patches and auxiliary files'
    )

    parser.add_option(
        '--config',
        action = 'store_true',
        dest = 'config',
        default = False,
        help = 'return a value from the configuration file (/etc/g-octave.cfg)'
    )

    parser.add_option(
        '--list-raw',
        action = 'store_true',
        dest = 'list_raw',
        default = False,
        help = 'show a list of packages available to install (a package per line, without colors) and exit'
    )

    options, args = parser.parse_args()

    if not options.colors:
        portage.output.nocolor()

    from g_octave.config import Config
    from g_octave.fetch import fetch

    conf_prefetch = Config(True)

    if options.config:
        try:
            log.info('Returning configuration data.')
            print(conf_prefetch.__getattr__(args[0]))
        except:
            log.error('Invalid configuration key: %s' % args[0])
            return os.EX_DATAERR
        return os.EX_OK

    from g_octave.package_manager import Portage, Pkgcore, Paludis

    if conf_prefetch.package_manager == 'portage':
        log.info('Your package manager is: Portage')
        pkg_manager = Portage(options.ask, options.verbose, options.pretend, options.oneshot, not options.colors)
    elif conf_prefetch.package_manager == 'pkgcore':
        log.info('Your package manager is: Pkgcore')
        pkg_manager = Pkgcore(options.ask, options.verbose, options.pretend, options.oneshot, not options.colors)
    elif conf_prefetch.package_manager == 'paludis':
        log.info('Your package manager is: Paludis')
        pkg_manager = Paludis(options.ask, options.verbose, options.pretend, options.oneshot, not options.colors)
    else:
        log.error('Invalid package manager: %s' % conf_prefetch.package_manager)
        out.eerror('Invalid package manager: %s' % conf_prefetch.package_manager)
        return os.EX_CONFIG

    # checking if the package manager is installed
    if not pkg_manager.is_installed():
        log.error('Package manager not installed: %s' % conf_prefetch.package_manager)
        out.eerror('Package manager not installed: %s' % conf_prefetch.package_manager)
        return os.EX_CONFIG

    # checking if the current user is allowed to run g-octave
    current_user = getpass.getuser()
    if current_user not in pkg_manager.allowed_users():
        log.error(
            'The current user (%s) can\'t run the current selected '
            'package manager (%s)' % (current_user, conf_prefetch.package_manager)
        )
        out.eerror(
            'The current user (%s) can\'t run the current selected '
            'package manager (%s)' % (current_user, conf_prefetch.package_manager)
        )
        return os.EX_NOPERM

    # checking if our overlay is correctly added to PORTDIR_OVERLAY
    if not pkg_manager.check_overlay(conf_prefetch.overlay, out):
        log.error('Overlay not properly configured.')
        return os.EX_CONFIG

    if has_fetch:
        log.info('You can fetch package databases.')
        updates = fetch()
        if updates is None:
            log.error('Invalid db_mirror value.')
            out.eerror('Your db_mirror value is invalid. Change it, or leave it empty to use the default.')
            return os.EX_CONFIG


        # checking if we have a package database
        if updates.need_update() and not options.sync:
            log.error('No package database found.')
            out.eerror('Please run "g-octave --sync" to download a package database!')
            return os.EX_USAGE

        if options.sync:

            log.info('Searching updates ...')
            out.einfo('Searching updates ...')

            if not updates.fetch_db():
                log.info('No updates available')
                out.einfo('No updates available')
            updates.extract()

            return os.EX_OK
    else:
        log.info('You can\'t fetch package databases.')
        if options.sync:
            log.error('You can\'t fetch package databases.')
            out.eerror('"--sync" not available, please install g-octave-9999 if you want this.')
            return os.EX_USAGE

    conf = Config()

    from g_octave.description import Description
    from g_octave.description_tree import DescriptionTree
    from g_octave.ebuild import Ebuild, EbuildException
    from g_octave.overlay import create_overlay

    if options.list_raw:
        log.info('Raw list of available packages.')
        tree = DescriptionTree()
        for pkg in tree.packages():
            print(pkg)
        return os.EX_OK
    elif options.list:
        log.info('Listing available packages.')
        tree = DescriptionTree()
        print(portage.output.blue('Available packages:'))
        print()
        packages = tree.list()
        for category in packages:
            print(
                portage.output.blue('Category:'),
                portage.output.white(category)
            )
            print()
            for pkg in packages[category]:
                print(
                    portage.output.green('    Package:'),
                    portage.output.white(pkg)
                )
                print(
                    portage.output.green('    Available versions:'),
                    portage.output.red(', '.join(packages[category][pkg]))
                )
                print()
        return os.EX_OK
    elif options.update:
        pass
    elif len(args) == 0:
        log.error('You need provide an argument.')
        out.eerror('You need provide an argument.')
        return os.EX_USAGE
    elif len(args) > 1:
        log.error('g-octave can install only one package at once.')
        out.eerror('At this moment g-octave can install only one package at once')
        return os.EX_USAGE

    # if we're alive yet, we have a package to install! :D
    # or a search to do! :P

    # check if use said that want the live version in some place
    use_scm = conf.use_scm.lower() == 'true' or options.scm

    # if the user said that don't want the live version with --no-scm,
    # this is mandatory
    if options.no_scm:
        use_scm = False

    create_overlay(options.force_all)

    if len(args) > 0:

        if options.search:
            log.info('Searching for packages: %s' % args[0])
            tree = DescriptionTree()
            print(
                portage.output.blue('Search results for '),
                portage.output.white(args[0]),
                portage.output.blue(':\n'),
                sep = ''
            )
            packages = tree.search(args[0])
            for pkg in packages:
                print(
                    portage.output.green('Package:'),
                    portage.output.white(pkg)
                )
                print(
                    portage.output.green('Available versions:'),
                    portage.output.red(', '.join(packages[pkg]))
                )
                print()
            return os.EX_OK

        log.info('Processing a package: %s' % args[0])
        try:
            ebuild = Ebuild(
                args[0], # pkg atom
                options.force or options.force_all, # force
                pkg_manager=pkg_manager, # package manager
                scm = use_scm, # want to use the live version?
            )
        except EbuildException:
            log.error('Package not found: %s' % args[0])
            out.eerror('Package not found: %s' % args[0])
            return os.EX_DATAERR

        if options.info:
            log.info('Returning info about the package.')
            pkg = ebuild.description()
            print(portage.output.blue('Package:'), portage.output.white(str(pkg.name)))
            print(portage.output.blue('Version:'), portage.output.white(str(pkg.version)))
            print(portage.output.blue('Date:'), portage.output.white(str(pkg.date)))
            print(portage.output.blue('Maintainer:'), portage.output.white(str(pkg.maintainer)))
            print(portage.output.blue('Description:'), portage.output.white(str(pkg.description)))
            print(portage.output.blue('Categories:'), portage.output.white(str(pkg.categories)))
            print(portage.output.blue('License:'), portage.output.white(str(pkg.license)))
            print(portage.output.blue('Url:'), portage.output.white(str(pkg.url)))
            return os.EX_OK

        atom, catpkg = ebuild.create()

    if options.unmerge:
        log.info('Calling the package manager to uninstall the package.')
        ret = pkg_manager.uninstall_package(atom, catpkg)
    elif options.update:
        if len(args) > 0:
            log.info('Calling the package manager to update the package.')
            ret = pkg_manager.update_package(atom, catpkg)
        else:
            log.info('Calling the package manager to update all the installed packages.')
            ret = pkg_manager.update_package()
    else:
        log.info('Calling the package manager to install the package.')
        ret = pkg_manager.install_package(atom, catpkg)

    if ret != os.EX_OK:
        log.error('"%s" returned an error.' % conf.package_manager)
        out.eerror('"%s" returned an error.' % conf.package_manager)
        sys.exit(ret)

    if options.unmerge and len(pkg_manager.post_uninstall) > 0:
        log.info(' '.join(pkg_manager.post_uninstall))
        print()
        for i in pkg_manager.post_uninstall:
            out.einfo(i)

    log.info('Finishing g-octave... all OK!')
    return os.EX_OK


if __name__ == '__main__':

    from g_octave.exception import *

    return_code = os.EX_OK

    try:
        return_code = main()
    except ConfigException as error:
        log.error('Config class error - %s' % error)
        out.eerror('Config class error - %s' % error)
        return_code = os.EX_CONFIG
    except DescriptionException as error:
        log.error('Description class error - %s' % error)
        out.eerror('Description class error - %s' % error)
        return_code = os.EX_SOFTWARE
    except DescriptionTreeException as error:
        log.error('DescriptionTree class error - %s' % error)
        out.eerror('DescriptionTree class error - %s' % error)
        return_code = os.EX_SOFTWARE
    except EbuildException as error:
        log.error('Ebuild class error - %s' % error)
        out.eerror('Ebuild class error - %s' % error)
        return_code = os.EX_SOFTWARE
    except FetchException as error:
        log.error('Fetch module error - %s' % error)
        out.eerror('Fetch module error - %s' % error)
        return_code = os.EX_SOFTWARE
    except OSError as error:
        log.error('Operating System error - %s' % error)
        out.eerror('Operating System error - %s' % error)
        out.eerror('Try run "g-octave" as root.')
        return_code = os.EX_OSERR
    except IOError as error:
        log.error('I/O error - %s' % error)
        out.eerror('I/O error - %s' % error)
        out.eerror('Try run "g-octave" as root.')
        return_code = os.EX_IOERR
    except KeyError as error:
        log.error('Key error - %s' % error)
        out.eerror('Key error - %s' % error)
        out.eerror('Probably you have more than one overlay configured to use with g-octave')
        out.eerror('Try remove the oldest and maintain only the overlay actually in use.')
        return_code = os.EX_SOFTWARE
    except Exception as error:
        log.error('Unknown error - %s' % error)
        out.eerror('Unknown error - %s' % error)
        return_code = os.EX_SOFTWARE

    if return_code not in [os.EX_OK, os.EX_CONFIG, os.EX_USAGE, os.EX_DATAERR, os.EX_NOPERM]:
        out.einfo('If you fell that this is a bug, please report to us.')
        out.einfo(__issue_tracker)

    sys.exit(return_code)
