#!/usr/bin/python
# FORG.py
# $Id: forg.py,v 1.15 2001/09/02 17:01:42 s2mdalle Exp $
# Written by David Allen <mda@idatar.com>
#
# This file contains the core FORG class, which handles most of the logic
# and content.  The actual GUI around it is handled by TkGui.py.
# Theoretically, you could replace TkGui with GtkGui and it would work, but
# not really, since you'd have to rewrite the Tkinter specific parts of
# GUI*.py
#
# This object is meant as a wrapper for other things that subclass ContentFrame
# This should simply be the object that is packed into the main GUI window
# and be a subclass of Tkinter.Frame.
#
# This object knows how to download resources and display them properly by
# using the other classes in the FORG.  It basically is just a shell of a
# Tkinter.Frame that contains the logic for using the right classes in the
# right way.  So without navigation, this contains all that is needed to
# implement the functionality in other programs using just this class (and of
# course the other things that it needs)
#
#  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.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
##############################################################################
# System wide imports
from Tkinter   import *                # Tk interface
from string    import *                # String manipulation
from threading import *                # Threads
import Pmw                             # Python Mega Widgets
import os                              # Operating system stuff
import socket                          # Socket communication
import sys
import tkFileDialog

# Non-GUI FORG specific imports
from gopher import *
import utils
import GopherConnection
import AskForm
import GopherResource
import GopherResponse
import Cache
import Options
import Associations
import List
import ListNode
import State

# GUI Specific
import GUIAskForm
import GUIDirectory
import GUIError
import GUIFile
import GUIQuestion
import GUISaveFile
import GUISearch
import ResourceInformation
import Dialogs

#--------------------------- VERSION ------------------------------------
VERSION = '0.5.1'
#--------------------------- VERSION ------------------------------------

FORGException = "Some error came up. I sure hope it came with a real message"
FORGContentException = "Content was missing probably"

def getVersion():
    return VERSION

class FORG(Frame):
    verbose = None
    
    def __init__(self, parent_widget, resource, response=None,
                 messageBar=None,
                 parent_object=None):
        Frame.__init__(self, parent_widget)
        self.parent_widget = parent_widget
        self.parent_object = parent_object
        self.response      = response
        self.resource      = resource
        self.mb            = messageBar
        self.opts          = Options.program_options  # Global alias

        self.locations      = List.List()
        self.navList        = List.List()

        self._createPopup()
        
        self.currentContent = None

        # Go wherever the user intended us to go.
        self.goElsewhere(resource)
        
        return None
    def getCache(self):
        """Return the cache object being used"""
        return Options.program_options.getCache()
    def getMessageBar(self):
        """Return the message bar being used."""
        return self.mb
    def setMessageBar(self, newmb):
        """Set the message bar object."""
        self.mb = newmb
        return self.getMessageBar()

    def dumpQueue(self, *args):
        print "QUEUE IS:"
        def fn(node, s=self):
            r = node.getData().getResource()
            print r.toURL()
            
        self.navList.traverse(fn)

    def stats(self, *args):
        buffer = "%s item(s) in the queue\n" % self.navList.countNodes()
        buffer = buffer + Options.program_options.cache.getCacheStats() + "\n"

        if self.verbose:
            print "QUEUE IS:"
            def fn(node, s=self):
                r = node.getData().getResource()
                print r.toURL()
            
        self.navList.traverse(fn)
            
        self.genericMessage(buffer)
    
    def find(self, term, caseSensitive=None, lastIdentifier=None):
        """This find method forwards all arguments to the child widget and
        does no processing.  See the documentation for ContentFrame.find for
        information on what parameters mean and what the method does."""
        # Pass on to the child verbatim.
        return self.child.find(term, caseSensitive, lastIdentifier)

    def genericMessage(self, str, title='Information:'):
        """Given a string, pops up a dialog with the appropriate title and
        content string."""
        x = Dialogs.InformationDialog(self, str, title)
        return None

    def genericError(self, errstr, title='Error:'):
        """Given an error string, pops up a dialog with the same title and
        text, alerting the user to something or other."""
        x = Dialogs.ErrorDialog(self, errstr, title)
        return None
    
    def getResource(self):
        """Returns the stored GopherResource object that was used to create
        this object."""
        return self.resource
    
    def getResponse(self):
        """Returns the stored GopherResponse object corresponding to the
        resource passed during the creation of this object"""
        return self.response

    def getGopherResponse(self):
        """This fetches the resource handed to the object from the network
        if necessary."""
        self.conn = GopherConnection.GopherConnection()
        msg = "Done"
        try:
            self.response = self.conn.getResource(resource=self.resource,
                                                  msgBar=self.mb)
        except GopherConnection.GopherConnectionException, estr:
            raise FORGException, estr
        except socket.error, err:
            try:
                if len(err) >= 2:
                    msg = "Can't fetch: errcode %d: %s" % (err[0], err[1])
                else:
                    msg = "Can't fetch: error %s" % err[0]
            except AttributeError:  # This is really weird, don't ask.
                msg =  "Can't fetch - unknown error."
            err = "getGopherResponse: %s" % err
            raise FORGException, err

        if self.response.getError():
            raise FORGException, self.response.getError()

        # Turn the green light back on, since we're done with the transmission.
        self.opts.greenLight()
        return None

    def popupMenu(self, event):
        """Display pop-up menu on right click on a message"""
        self.popup.tk_popup(event.x_root, event.y_root)

    def saveFile(self, *args):
        """Pop up a dialog box telling the user to choose a filename to save
        the file as."""
        dir = os.path.abspath(os.getcwd())
        filename = tkFileDialog.asksaveasfilename(initialdir=dir)
        
        listNode = self.navList.getCurrent()
        response = listNode.getData().getResponse()

        if filename and filename != "":
            try:
                response.writeToFile(filename)
            except IOError, errstr:
                self.genericError("Couldn't write data to\n%s:\n%s" %
                                  (filename, errstr))

        return None

    def _createPopup(self):
        """Creates Pop-up menu on right click on a message"""
        self.popup = Menu(self)
        self.popup['tearoff'] = FALSE
        self.popup.add_command(label='Save', command=self.saveFile)
        self.popup.add_command(label='Back', command=self.goBackward)
        self.popup.add_command(label='Forward', command=self.goForward)

    def goForward(self, *rest):
        """Navigate forward"""
        try:
            node = self.navList.getNext()
            data = node.getData()

            # In case we are going forward to an error message...
            if not data.getResponse():
                # Remove current item in the list.
                self.navList.removeCurrent()

                # Go to this location again.
                return self.goElsewhere(data.getResource())

            self.createResponseWidget()
            self.changeContent(self.child)
        except List.ListException, errstr:
            if self.verbose:
                print "Couldn't get next: %s" % errstr
        return None

    def goBackward(self, *rest):
        """Navigate backward."""
        try:
            node = self.navList.getPrev()
            state = node.getData()

            # In case we are going back to an error message...
            if not state.getResponse():
                # Remove current item in the list.
                self.navList.removeCurrent()

                # Go to this location again.
                return self.goElsewhere(state.getResource())

            self.response = state.getResponse()
            self.resource = state.getResource()
            self.createResponseWidget()
            node.setData(State.State(self.response, self.resource, self.child))
            self.changeContent(self.child)
        except List.ListException, errstr:
            if self.verbose:
                print "Couldn't get prev: %s" % errstr

    def downloadResource(self, res):
        """Downloads the resource from the network and redisplays"""
        self.resource = res

        try:
            self.getGopherResponse()
        except FORGException, estr:
            self.genericError("Error fetching resource:\n%s" % estr)
            return None
            
        self.createResponseWidget()
        self.navList.insert(ListNode.ListNode(State.State(self.response,
                                                          self.resource,
                                                          self.child)))
        self.changeContent(self.child)
        return None

    def reload(self):
        newthread = Thread(group=None,
                           target=self.reloadResource,
                           name="Reload Thread",
                           args=())
        # Thread is responsible for adding the resource to the queue list
        # It will run and do its thing and when the function returns, the \
        # thread ends.
        newthread.start()
        return None

    def reloadResource(self):
        """Reloads from the network the current resource."""
        try:
            self.getGopherResponse()
        except FORGException, estr:
            self.genericError("Error fetching resource:\n%s" % estr)
            return None
        
        self.createResponseWidget()
        s = State.State(self.response, self.resource, self.child)
        self.navList.getCurrent().setData(s)
        self.changeContent(self.child)
        return None

    def getCurrentURL(self):
        return self.resource.toURL()
    
    def goElsewhere(self, resource, usecache=1, *args):
        """This is a wrapper for dealing with the download thread.  Pass it
        one resource as an argument, and that resource will be downloaded in
        the background.  Optionally, if the file exists in the cache, it will
        be loaded from there if usecache is true."""

        self.resource   = resource
        self.response   = None
        optionsUseCache = Options.program_options.getOption("use_cache")
        
        # Attempt to load the response object out of the cache.
        if usecache and optionsUseCache and not resource.getDataBlock():
            # Don't try to pull something out of cache unless it doesn't have
            # a data block. If it does, we have to submit information
            utils.msg(self.mb, "Looking for document in cache...")
            self.response = Options.program_options.cache.uncache(resource)

        if not self.response:
            utils.msg(self.mb,
                      "Document not in cache. Fetching from network.")
            # We need to fetch this document from the network.
            # Signal the download thread to go to work, and get out.
            newthread = Thread(group=None,
                               target=self.downloadResource,
                               name="Download Thread",
                               args=(self.resource,))
            # Thread is responsible for adding the resource to the queue list
            newthread.start()
            return
        else:
            utils.msg(self.mb, "Loading document from cache to display.")
            self.createResponseWidget()
            s = State.State(self.response, self.resource, self.child)
            self.navList.insert(ListNode.ListNode(s))
            self.changeContent(self.child)
        return None

    def changeContent(self, newwid):
        """Changes the current main content of the window to newwid"""
        if self.currentContent:
            self.currentContent.pack_forget()

        if newwid:
            utils.msg(self.mb, "Updating display...")            
            newwid.pack(expand=1, fill='both')
            newwid.pack_content()
            utils.msg(self.mb, "Done")

        self.currentContent = newwid

        try:
            self.parent_object.change_content_hook(self.getResource())
        except:
            # If it doesn't exist, or fails for some reason, that's the
            # parents problem, not ours.
            pass
        
        return self.currentContent
    
    def createResponseWidget(self):
        """Take the response already fetched and turn it into a child widget.
        This only works if the resource has already been fetched from the
        network."""
        
        if not self.response:
            raise(FORGException,
                  "createResponseWidget: No valid response present")
        if not self.resource:
            raise(FORGException,
                  "createResponseWidget: No valid resource present")

        cfilename = None
        
        if self.opts.getOption('use_cache'):
            utils.msg(self.mb, 'Caching data...')
            cfilename = ''

            _resr = self.resource.shouldCache()
            _resp = self.response.shouldCache()
            
            if not self.resource.isAskType() and _resr and _resp:
                # Don't try to cache ASK blocks.  It will only throw an
                # exception since caching them isn't a very stellar idea.
                try:
                    cfilename = self.opts.cache.cache(resp=self.response,
                                                      resource=self.resource)
                except Cache.CacheException, exceptionstr:
                    self.genericError(exceptionstr)

        r = self.response

        if self.response.getTypeCode() == RESPONSE_INDEXS:
            self.child = GUISearch.GUISearch(parent_widget=self,
                                             parent_object=self,
                                             resp=self.response,
                                             resource=self.resource,
                                             filename=cfilename)
        elif self.response.__class__ == AskForm.AskForm:
            try:
                self.child = GUIAskForm.GUIAskForm(parent_widget=self,
                                                   parent_object=self,
                                                   resp=self.response,
                                                   resource=self.resource,
                                                   filename=cfilename)
            except Exception, errstr:
                print "******************************************************"
                print "Caught ", Exception, " with error ", errstr, " ASK"
                self.child = Label(self, text="Congrats!  You've found a bug!")
        elif r.getData() != None and not r.looksLikeDir(r.getData()):
            if cfilename != None:
                assoc = self.opts.associations.getAssociation(cfilename)
                
                if assoc != None:
                    self.LAUNCH_ME = [cfilename, assoc]
                    
            if self.response.getTypeCode() == RESPONSE_FILE:
                self.child = GUIFile.GUIFile(parent_widget=self,
                                             parent_object=self,
                                             resp=self.response,
                                             resource=self.resource,
                                             filename=cfilename)
            else:
                self.child = GUISaveFile.GUISaveFile(parent_widget=self,
                                                     parent_object=self,
                                                     resp=self.response,
                                                     resource=self.resource,
                                                     filename=cfilename)

        else:  # There is no data, display a directory entry
            ma = {
                "Back"       : self.goBackward,
                "Forward"    : self.goForward,
                "About FORG" : self.about }
                
            self.child = GUIDirectory.GUIDirectory(parent_widget=self,
                                                   parent_object=self, 
                                                   resp=self.response,
                                                   resource=self.resource,
                                                   filename=cfilename,
                                                   menuAssocs=ma)

    def about(self, *args):
        """Display the about box."""
        Pmw.aboutversion(getVersion())
        Pmw.aboutcopyright('Copyright 2000, 2001')
        Pmw.aboutcontact(
            'This program is licensed under the GNU General Public License\n' +
            'For more information, please see\n' +
            'http://www.gnu.org/')
        self._about_dialog = Pmw.AboutDialog(self, applicationname='FORG')
        # self._about_dialog.show()
        return None


################################### MAIN CODE ###############################

if __name__ == '__main__':
    # Require TkGui only if we are running the application stand-alone.
    from TkGui import *

    print "Starting the FORG"

    arr = sys.argv
    
    def main(item):
        try:
            app = TkGui(item)
            app.mainloop()
        except KeyboardInterrupt:
            app.quit()
            exit()

    url = ""

    try:
        url = arr[1]
    except:
        url = "gopher://gopher.floodgap.com:70/"

    if find(lower(url[:]), "gopher://") == -1:
        url = "gopher://" + url

    # Start the program
    print "Starting program with \"%s\"" % url
    main(url)

