#!/usr/local/bin/python2.7

from __future__ import nested_scopes
import string, sys, os, urllib2

from Ciphon import install, config, check
    
__version__ = "$Revision: 1.14 $"

def debugHook(type, value, tb):
    """Function to invoke pdb on exceptions, thanks to Thomas Heller for the code"""
    import traceback, pdb
    traceback.print_exception(type, value, tb)
    print
    # ...then start the debugger in post-mortem mode.
    pdb.pm()

def reportHook(type, value, tb):
    """Function to allow users to report errors to bugs mailing lists"""
    import traceback
    exceptionVal = traceback.format_exception(type, value, tb)
    exceptionVal = " ".join(exceptionVal)
    sys.stderr.write(exceptionVal)
    sys.stderr.write("\n")
    sendBugReport(exceptionVal)
    sys.stderr.write("Exiting ciphon now.\n")
    sys.exit(1)            
    
     
def sendBugReport(exceptionVal):
    """Function to ask the user whether to send a bug report and to send out the
    bug report if the user requests it."""
    sys.stderr.write("An uncaught exception has been thrown.\n")
    sys.stderr.write("Do you want to send an automatic bug report (Y/n)? ")
    response = raw_input(" ")
    mesg = ""
    if (len(response) == 0) or  (response[0].upper() == "Y"):
        import smtplib
        fromAddr = raw_input("Please enter your email address(e.g. user@user.com): ")
        fromAddr = fromAddr.strip()

        
        sys.stdout.write("Please enter any relevant information about the bug.\n");
        sys.stdout.write("Press enter on a blank line to quit.\n");
        
        while 1:
            try:
                temp = raw_input()
            except EOFError:
                break;
            if not temp:
                break
            mesg += temp + "\n"
        mesg += "\n"
        mesg += exceptionVal
        headers = "From: %s\n" % fromAddr
        headers += "To: pythonsiphon-bugs@lists.sourceforge.net\n"
        headers += "Subject: Incoming bug report from revision " + __version__ + "\n"
        mailServer = smtplib.SMTP('localhost')
        mailServer.sendmail(fromAddr,
                            "pythonsiphon-bugs@lists.sourceforge.net",
                            headers + mesg)
        mailServer.quit()
        sys.stdout.write("E-mail sent\n")
    
class ReadEvalLoop:
    """Class to run main read-eval loop"""
    
    def __init__(self):
        """Initialize class variables"""

        self.__commands = {'help': self.__mainHelp,
                           'quit': self.__quit,
                           'exit': self.__quit}
        self.__help = {'quit': 'Exit ciphon',
                       'exit': 'Exit ciphon',
                       'help': 'help'}
        self.__matches = []
        
    def __getCompletion(self, text, state):
        """Function to return the next completion for text"""

        if state == 0:
            self.__matches = []
            length = len(text)
            # slightly hairy list comprehension that returns the commands that
            # match text
            for word in [x for x in self.__commands.keys() if x[:length] == text]:
                self.__matches.append(word)
        
        try:
            return self.__matches[state]
        except IndexError:
            return None
        

    def begin(self):
        """start read-eval loop that reads input and dispatches based on that"""
        sys.stdout.write("Ciphon 0.3.5 Copyright 2001-2002 Suchandra Thapa\n")
        sys.stdout.write("See license.txt for license information\n")
        if config.getConfig().getParameter('readline') == 'true':
            import readline
            readline.parse_and_bind("tab: complete")
            readline.set_completer(self.__getCompletion)
        while 1:
            # basically the read-eval loop works by reading the first word
            # of the user's input, if this word is a key in  self.__commands, the
            # function associated with that key is run on the rest of the user's
            # input. Otherwise the help function is run
            input = ''
            try:
                input = raw_input('ciphon> ')
            except EOFError:
                sys.exit(0)
            input = string.split(input)
            self.dispatch(input)        

    def dispatch(self, command):
        """Dispatch input given on the command line to the correct handler"""
        if len(command) == 0:
            handler = ['help']
        if command[0] in self.__commands.keys():
            handler = self.__commands[command[0]]
        else:
            handler = self.__mainHelp
        handler(command)
        
    def __mainHelp(self, input):
        """Displays help for main eval loop and commands"""
        if len(input) == 1 or not (input[1] in self.__help.keys()):
            sys.stderr.write("\nAllowed commands: \n")
            for key in self.__help.keys():
                sys.stderr.write("%s\n" % key)
            sys.stderr.write("\nEnter help [command] for more help\n")
        else:
            sys.stdout.write("%s\n" % self.__help[input[1]])

    def installModule(self, module):
        """Call module.installModule on help and command dictionaries"""

        module.installModule(self.__commands, self.__help)

    def __quit(self, input):
        sys.exit(0)


# check to see if ~/.ciphon exists, if not try to create it and download the
# package and server lists
ciphonDir = os.path.expanduser('~/.ciphon')

if not os.path.isdir(ciphonDir):
    try:
        os.mkdir(ciphonDir)
        try:
            url = "ftp://ftp.community.tummy.com/pub/python-packages/servers.xml"
            data = urllib2.urlopen(url)
            fileHandle = open(os.path.join(ciphonDir, 'servers.xml'), 'w')
            while 1:
                buffer = data.read(8192)
                if buffer == "":
                    break
                fileHandle.write(buffer)
        except urllib2.URLError:
            sys.stderr.write("Can't get servers.xml, quitting")
            sys.exit(1)
        except IOError:
            sys.stderr.write("Error writing to servers.xml, quitting")
            sys.exit(1)            
    except OSError:
        sys.stderr.write("Can't create ~/.ciphon, directory exists")
    
main  = ReadEvalLoop()

# all modules being installed need to provide a function called installModule
# this function takes two dictionaries (command, help) and inserts entries
# of the form command => function for the command dictionary and topic => helptext

# to install a module just import it and call its main.installModule(modulename)

# the installModule function installs the toplevel commands for each
# module

main.installModule(install)
main.installModule(config)
main.installModule(check)

# grab new versions of servers.xml and packages.xml
if config.getConfig().getParameter('autoupdate') == 'true':
    sys.stdout.write("Updating servers.xml and packages.xml, please wait\n")    
    try:
        url = "ftp://ftp.community.tummy.com/pub/python-packages/servers.xml"
        data = urllib2.urlopen(url)
        fileHandle = open(os.path.join(ciphonDir, 'servers.xml'), 'w')
        while 1:
            buffer = data.read(8192)
            if buffer == "":
                break
            fileHandle.write(buffer)
        fileHandle.close()
        
        url = "ftp://ftp.community.tummy.com/pub/python-packages/packages.xml"
        data = urllib2.urlopen(url)
        fileHandle = open(os.path.join(ciphonDir, 'packages.xml'), 'w')
        while 1:
            buffer = data.read(8192)
            if buffer == "":
                break
            fileHandle.write(buffer)
        fileHandle.close()
    except urllib2.URLError:
        sys.stderr.write("Can't obtain server or package lists, giving up\n")
    except IOError:
        sys.stderr.write("Can't write server or package list, giving up\n")

# create config file if it doesn't exist
config.getConfig().writeConfig()

# neat little trick from Thomas Heller to catch and report exceptions or
# allow automatic debugging
if config.getConfig().getParameter('debug') == 'true':
    sys.excepthook = debugHook
else:
    sys.excepthook = reportHook
    
# start the read-eval loop
# if there are command line arguments assume the user wants to run ciphon
# in command line mode
if len(sys.argv) > 1:  
    main.dispatch(sys.argv[1:])
else:
    main.begin()
