#!/usr/bin/python2
#
#  KeepNote - note-taking and organization
#  Copyright (c) 2008-2011 Matt Rasmussen
#  Author: Matt Rasmussen <rasmus@mit.edu>
#
# 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; version 2 of the License.
#
# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
#

# python imports
import exceptions
import sys
import os
from os.path import basename, dirname, realpath, join, isdir
import time
import optparse
import thread
import threading
import traceback



#=============================================================================
# KeepNote import

"""
Three ways to run KeepNote

bin_path = os.path.dirname(sys.argv[0])

(1) directly from source dir
    
    pkgdir = bin_path + "../keepnote"
    basedir = pkgdir
    sys.path.append(pkgdir)

    src/bin/keepnote
    src/keepnote/__init__.py
    src/keepnote/images
    src/keepnote/rc

(2) from installation location by setup.py 

    pkgdir = keepnote.get_basedir()
    basedir = pkgdir

    prefix/bin/keepnote
    prefix/lib/python-XXX/site-packages/keepnote/__init__.py
    prefix/lib/python-XXX/site-packages/keepnote/images
    prefix/lib/python-XXX/site-packages/keepnote/rc
    

(3) windows py2exe dir

    pkgdir = bin_path
    basedir = bin_path

    dir/keepnote.exe
    dir/library.zip
    dir/images
    dir/rc

"""

# try to infer keepnote lib path from program path
pkgdir = dirname(dirname(realpath(sys.argv[0])))
if os.path.exists(join(pkgdir, "keepnote", "__init__.py")):
    sys.path.insert(0, pkgdir)
    import keepnote

    # if this works we know we are running from src_path (1)
    basedir = keepnote.get_basedir()

else:
    # try to import from python path
    import keepnote
    
    # sucessful import, therefore we are running with (2) or (3)
    
    # attempt to use basedir for (2)
    basedir = keepnote.get_basedir()
    
    if not isdir(join(basedir, "images")):
        # we must be running (3)
        basedir = dirname(realpath(sys.argv[0]))

keepnote.set_basedir(basedir)




#=============================================================================
# keepnote imports
import keepnote
from keepnote.commands import get_command_executor, CommandExecutor
from keepnote.teefile import TeeFileStream
import keepnote.compat.pref
_ = keepnote.translate



#=============================================================================
# command-line options

o = optparse.OptionParser(usage=
                "%prog [options] [NOTEBOOK]")
o.set_defaults(default_notebook=True)
o.add_option("-c", "--cmd", dest="cmd",
             action="store_true",
             help="treat remaining arguments as a command")
o.add_option("-l", "--list-cmd", dest="list_cmd",
             action="store_true",
             help="list available commands")
o.add_option("-i", "--info", dest="info",
             action="store_true",
             help="show runtime information")
o.add_option("--no-gui", dest="no_gui",
             action="store_true",
             help="run in non-gui mode")
o.add_option("-t", "--continue", dest="cont",
             action="store_true",
             help="continue to run after command execution")
o.add_option("", "--show-errors", dest="show_errors",
             action="store_true",
             help="show errors on console")
o.add_option("--no-show-errors", dest="show_errors",
             action="store_false",
             help="do not show errors on console")
o.add_option("--no-default", dest="default_notebook",
             action="store_false",
             help="do not open default notebook")
o.add_option("", "--newproc", dest="newproc",
             action="store_true",
             help="start KeepNote in a new process")
o.add_option("-p", "--port", dest="port",
             default=None,
             type="int",
             help="use a specified port for listening to commands")


#=============================================================================

def start_error_log(show_errors):
    """Starts KeepNote error log"""

    keepnote.init_error_log()
        
    # Test ability to write to file-like objects used to display errors.  
    # - if stderr is unavailable, create error message, else add to stream
    #   list. Do not exit.
    # Note: this code-section is necessary to allow Linux-users the option of
    #       launching KeepNote from a *.desktop file without having it  
    #       run in a terminal. In other words, 'Terminal=false' can be safely 
    #       added to the *.desktop file; without this code, adding 
    #       'Terminal=false' to the *.desktop file causes KeepNote 
    #       launch failure.
    
    stream_list = []
    stderr_test_str = "\n"
    stderr_except_msg = ""
    
    if show_errors:    
        try:            
            sys.stderr.write(stderr_test_str)
        except IOError:
            formatted_msg = traceback.format_exc().splitlines()
            stderr_except_msg = ''.join(
                ['** stderr - unavailable for messages - ',
                 formatted_msg[-1], "\n"])
        else:
            stream_list.append(sys.stderr)
    
    # if errorlog is unavailable, exit with error, else add to stream list.
    try:
        errorlog = open(keepnote.get_user_error_log(), "a")
    except IOError:
        sys.exit(traceback.print_exc())
    else:
        stream_list.append(errorlog)
    

    # redirect stderr
    sys.stderr = TeeFileStream(stream_list, autoflush=True)


    # write errorlog header
    keepnote.print_error_log_header()
    keepnote.log_message(stderr_except_msg)


def parse_argv(argv):
    """Parse arguments"""

    # set default arguments
    options = o.get_default_values()
    if keepnote.get_platform() == "windows":        
        options.show_errors = False
    else:
        options.show_errors = True

    # parse args and process
    (options, args) = o.parse_args(argv[1:], options)
    return options, args


def setup_threading():
    """Initialize threading environment"""

    import gtk.gdk
    import gobject

    if keepnote.get_platform() == "windows":
        # HACK: keep gui thread active
        def sleeper():
            time.sleep(.001)
            return True # repeat timer
        gobject.timeout_add(400, sleeper)

    else:
        gtk.gdk.threads_init()


def gui_exec(function, *args, **kw):
    """Execute a function in the GUI thread"""
    
    import gtk.gdk
    import gobject

    sem = threading.Semaphore()
    sem.acquire()

    def idle_func():
        gtk.gdk.threads_enter()
        try:
            function(*args, **kw)
            return False
        finally:
            sem.release() # notify that command is done
            gtk.gdk.threads_leave()
    gobject.idle_add(idle_func)

    # wait for command to execute
    sem.acquire()
    


def start_gui(argv, options, args, cmd_exec):
    import keepnote.gui

    # pygtk imports
    import pygtk
    pygtk.require('2.0')
    import gtk

    # setup threading environment
    setup_threading()

    # create app
    app = keepnote.gui.KeepNote(basedir)
    app.init()
    cmd_exec.set_app(app)
    need_gui = execute_command(app, argv)
    
    # begin gtk event loop
    if need_gui:
        gtk.main()


def start_non_gui(argv, options, args, cmd_exec):

    # read preferences
    app = keepnote.KeepNote(basedir)
    app.init()
    cmd_exec.set_app(app)
    execute_command(app, argv)


def execute_command(app, argv):
    """
    Execute commands given on command line

    Returns True if gui event loop should be started
    """
    
    options, args = parse_argv(argv)

    #------------------------------------------
    # process builtin commands
    if options.list_cmd:
        list_commands(app)
        return False
    
    if options.info:
        keepnote.print_runtime_info(sys.stdout)
        return False


    #------------------------------------------
    # process extended commands
    if options.cmd:
        # process application command (AppCommand)
        if len(args) == 0:
            raise Exception(_("Expected command"))

        command = app.get_command(args[0])
        if command:
            command.func(app, args)
        else:
            raise Exception(_("Unknown command '%s'") % args[0])
        
        # start first window
        if not options.no_gui:
            if len(app.get_windows()) == 0:
                app.new_window()
                return True

        return False

    #------------------------------------------
    # process a non-command

    if options.no_gui:
        return False

    if len(args) > 0:
        
        for arg in args:

            if keepnote.notebook.is_node_url(arg):
                # goto a node
                host, nodeid = keepnote.notebook.parse_node_url(arg)
                app.goto_nodeid(nodeid)

            elif keepnote.extension.is_extension_install_file(arg):
                # install extension
                if len(app.get_windows()) == 0:
                    app.new_window()

                app.install_extension(arg)

            else:
                # open specified notebook
                if len(app.get_windows()) == 0:
                    app.new_window()
                app.get_current_window().open_notebook(arg)

    else:
        # no arguments
        win = app.new_window()

        # open default notebook
        if len(app.get_windows()) == 1 and options.default_notebook:
            # TODO: finish
            # reopen all windows referenced by notebooks

            for path in app.pref.get("default_notebooks", default=[]):
                win.open_notebook(path, open_here=False)

    return True


def list_commands(app):
    """List available commands"""
    
    commands = app.get_commands()
    commands.sort(key=lambda x: x.name)

    print
    print "available commands:"
    for command in commands:
        print " " + command.name,
        if command.metavar:
            print " " + command.metavar,            
        if command.help:
            print " -- " + command.help,
        print



def main(argv):
    """Main execution"""    

    options, args = parse_argv(argv)
    
    # init preference dir
    keepnote.compat.pref.check_old_user_pref_dir()
    if not os.path.exists(keepnote.get_user_pref_dir()):
        keepnote.init_user_pref_dir()

    # start error log
    start_error_log(options.show_errors)

    # get command executor
    if options.newproc:
        main_proc = True
        cmd_exec = CommandExecutor()
    else:
        if options.no_gui:
            main_proc, cmd_exec = get_command_executor(
                execute_command, port=options.port)
        else:
            main_proc, cmd_exec = get_command_executor(
                lambda app, argv: gui_exec(
                    lambda: execute_command(app, argv)),
                port=options.port)    

    if main_proc:
        # initiate main process
        if options.no_gui:
            start_non_gui(argv, options, args, cmd_exec)
        else:
            start_gui(argv, options, args, cmd_exec)
    else:
        # this is a command process, send command to main process
        cmd_exec.execute(argv)

    # wait for other threads to close application
    if options.cont:
        while True:
            time.sleep(1000)

#=============================================================================
# start main function
# catch any exceptions that occur
try:
    main(sys.argv)
except exceptions.SystemExit, e:
    # sys.exit() was called
    pass
except Exception, e:
    traceback.print_exc()
    sys.stderr.flush()


