#!/usr/bin/python

'''Command line Apport user interface.

Copyright (C) 2007 Michael Hofmann <mh21@piware.de>

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
the full text of the license.
'''

# Web browser support:
#    w3m, lynx: do not work
#    elinks: works

import os.path, os, sys, subprocess, re, errno
import tty, termios
from datetime import datetime

try:
    from gettext import gettext as _
    import apport.ui
except ImportError, e:
    # this can happen while upgrading python packages
    print >> sys.stderr, 'Could not import module, is a package upgrade in progress? Error:', e
    sys.exit(1)

class CLIDialog:
    '''Command line dialog wrapper.'''

    def __init__(self, heading, text):
        self.heading = '\n*** ' + heading + '\n'
        self.text = text
        self.keys = []
        self.buttons = []
        self.visible = False

    def raw_input_char(self, text):
        """ raw_input, but read only one character """

        sys.stdout.write(text + ' ')

        file = sys.stdin.fileno()
        saved_attributes = termios.tcgetattr(file)
        attributes = termios.tcgetattr(file)
        attributes[3] = attributes[3] & ~(termios.ICANON)
        attributes[6][termios.VMIN] = 1
        attributes[6][termios.VTIME] = 0
        termios.tcsetattr(file, termios.TCSANOW, attributes)

        try:
            ch = str(sys.stdin.read(1))
        finally:
            termios.tcsetattr(file, termios.TCSANOW, saved_attributes)

        print
        return ch

    def show(self):
        self.visible = True
        print self.heading
        print self.text

    def run(self):
        if not self.visible:
            self.show()

        print
        try:
            # Only one button
            if len (self.keys) <= 1:
                self.raw_input_char(_('Press any key to continue...'))
                return 0
            # Multiple choices
            while True:
                print _('What would you like to do? Your options are:')
                for index, button in enumerate(self.buttons):
                    print '  %s: %s' % (self.keys[index], button)

                response = self.raw_input_char(_('Please choose (%s):') % ('/'.join(self.keys)))
                try:
                    return self.keys.index(response[0].upper()) + 1
                except ValueError:
                    pass
        except KeyboardInterrupt:
            print
            sys.exit(1)

    def addbutton(self, button):
        self.keys.append(re.search('&(.)', button).group(1).upper())
        self.buttons.append(re.sub('&', '', button))
        return len(self.keys)


class CLIProgressDialog(CLIDialog):
    '''Command line progress dialog wrapper.'''

    def __init__(self, heading, text):
        CLIDialog.__init__(self, heading, text)
        self.progresscount = 0

    def set(self, progress = None):
        self.progresscount = (self.progresscount + 1) % 5
        if self.progresscount:
            return

        if progress != None:
            sys.stdout.write('\r%u%%' % (progress * 100))
        else:
            sys.stdout.write('.')
        sys.stdout.flush()

class CLIUserInterface(apport.ui.UserInterface):
    '''Command line Apport user interface'''

    def __init__(self):
        apport.ui.UserInterface.__init__(self)

    #
    # ui_* implementation of abstract UserInterface classes
    #

    def ui_present_crash(self, desktop_entry):
        date = datetime.strptime(self.report['Date'], '%a %b %d %H:%M:%S %Y')
        # adapt dialog heading and label appropriately
        if desktop_entry:
            name = desktop_entry.getName()
        elif self.report.has_key('ExecutablePath'):
            name = os.path.basename(self.report['ExecutablePath'])
        else:
            name = self.cur_package
        # translators: first %s: application name, second %s: date, third %s: time
        heading = _('%s closed unexpectedly on %s at %s.') % (name, date.date(), date.time())

        dialog = CLIDialog(
                heading,
                _('If you were not doing anything confidential (entering passwords or other\n'
                  'private information), you can help to improve the application by reporting\n'
                  'the problem.'))
        report = dialog.addbutton(_('&Report Problem...'))
        ignore = dialog.addbutton(_('Cancel and &ignore future crashes of this program version'))
        dialog.addbutton(_('&Cancel'))

        # show crash notification dialog
        response = dialog.run()

        if response == report:
            return {'action': 'report', 'blacklist': False}
        if response == ignore:
            return {'action': 'cancel', 'blacklist': True}
        # Fallback
        return {'action': 'cancel', 'blacklist': False}

    def ui_present_package_error(self):
        name = self.report['Package']
        dialog = CLIDialog(
                _('The package "%s" failed to install or upgrade.') % name,
                _('You can help the developers to fix the package by reporting the problem.'))
        report = dialog.addbutton(_('&Report Problem...'))
        dialog.addbutton(_('&Cancel'))

        response = dialog.run()

        if response == report:
            return 'report'
        # Fallback
        return 'cancel'

    def ui_present_kernel_error(self):
        dialog = CLIDialog (
                _('The kernel encountered a serious problem'),
                _('Your system might become unstable now and might need to be restarted.\n'
                  'You can help the developers to fix the problem by reporting it.'))
        report = dialog.addbutton(_('&Report Problem...'))
        dialog.addbutton(_('&Cancel'))

        response = dialog.run()

        if response == report:
            return 'report'
        # Fallback
        return 'cancel'

    def ui_present_report_details(self):
        dialog = CLIDialog (
                _('Send problem report to the developers?'),
                _('After the problem report has been sent, please fill out the form in the\n'
                  'automatically opened web browser.'))

        # report contents
        details = ''
        for key in self.report:
            details += key + ':'
            # string value
            if not hasattr(self.report[key], 'gzipvalue') and \
                hasattr(self.report[key], 'isspace') and \
                not self.report._is_binary(self.report[key]):
                lines = self.report[key].splitlines()
                if len(lines) <= 1:
                    details += ' ' + self.report[key] + '\n'
                else:
                    details += '\n'
                    for line in lines:
                        details += '  ' + line + '\n'
            else:
                details += ' ' + _('(binary data)') + '\n'

        # complete/reduced reports
        if self.report.has_key('CoreDump') and self.report.has_useful_stacktrace():
            complete = dialog.addbutton(_('&Send complete report (recommended; %s)') %
                    self.format_filesize(self.get_complete_size()))
            reduced = dialog.addbutton(_('Send &reduced report (slow Internet connection; %s)') %
                    self.format_filesize(self.get_reduced_size()))
        else:
            complete = dialog.addbutton(_('&Send report (%s)') %
                    self.format_filesize(self.get_complete_size()))
            reduced = None

        view = dialog.addbutton(_('&View report'))
        save = dialog.addbutton(_('&Keep report file for sending later or copying to somewhere else'))
        
        dialog.addbutton(_('&Cancel'))

        while True:
            response = dialog.run()

            if response == complete:
                return 'full'
            if response == reduced:
                return 'reduced'
            if response == view:
                try:
                    subprocess.Popen(["/usr/bin/sensible-pager"],
                            stdin=subprocess.PIPE,
                            close_fds=True).communicate(details)
                except IOError, e:
                    # ignore broken pipe (premature quit)
                    if e.errno == errno.EPIPE:
                        pass
                    else:
                        raise
                continue
            if response == save:
                print _('Problem report file:'), self.report_file
                return 'cancel'

            # Fallback
            return 'cancel'

    def ui_info_message(self, title, text):
        dialog = CLIDialog(title, text)
        dialog.addbutton(_('&Confirm'))
        dialog.run()

    def ui_error_message(self, title, text):
        dialog = CLIDialog(_('Error: %s') % title, text)
        dialog.addbutton(_('&Confirm'))
        dialog.run()

    def ui_start_info_collection_progress(self):
        self.progress = CLIProgressDialog (
                _('Collecting problem information'),
                _('The collected information can be sent to the developers to improve the\n'
                  'application. This might take a few minutes.'))
        self.progress.show()

    def ui_pulse_info_collection_progress(self):
        self.progress.set()

    def ui_stop_info_collection_progress(self):
        print

    def ui_start_upload_progress(self):
        self.progress = CLIProgressDialog (
                _('Uploading problem information'),
                _('The collected information is being sent to the bug tracking system.\n'
                  'This might take a few minutes.'))
        self.progress.show()

    def ui_set_upload_progress(self, progress):
        self.progress.set(progress)

    def ui_stop_upload_progress(self):
        print

if __name__ == '__main__':
    app = CLIUserInterface()
    if not app.run_argv():
        print >> sys.stderr, _('No pending crash reports. Try --help for more information.')
