#!/usr/bin/env python
#
# synarere -- a trivial, highly modular IRC bot.
# Copyright (C) 2010 Michael Rodriguez.
# Rights to this code are documented in ../docs/LICENSE.

'''Main program. This is mainly a caller to other functions, and basic routines.'''

# Import required Python modules.
import asyncore, getopt, os, signal, sys, time

# Import all source modules.
from src import *

def print_clo_help():
    '''Output command line options and their meanings.'''

    print '-c (--config) <config>: Specify the configuration file to use.'
    print '-d (--debug): Start in debug mode.'
    print '-h (--help): Output this message.'
    print '-n (--nofork): Do not fork into the background (will output log messages)'
    print '-p (--pdebug): Start in the Python Debugger.'

def on_sighup(signum, frame):
    '''Handle signal SIGHUP. This will rehash the configuration.'''

    instance.conf.rehash(True)

def on_sigint(signum, frame):
    '''Handle signal SIGINT. This will exit gracefully.'''

    instance.exit('Caught SIGINT (terminal interrupt)', signum)

def on_sigterm(signum, frame):
    '''Handle signal SIGTERM. This will exit gracefully.'''

    instance.exit('Caught SIGTERM', signum)

def main(argv):
    '''Our entry point.'''

    # Are we root?
    if os.geteuid() == 0:
        print 'synarere will not run as root.'
        sys.exit(os.EX_SOFTWARE)

    # Parse command line options and parameter list.
    try:
        opts, args = getopt.getopt(argv, 'c:dhnp', ['config=', 'debug', 'help', 'nofork', 'pdebug'])
    except getopt.GetoptError, err:
        print '%s\n' % err
        print_clo_help()
        sys.exit(os.EX_USAGE)

    for opt, arg in opts:
        if opt in ('-c', '--config'):
            var.config_file = arg
        elif opt in ('-d', '--debug'):
            var.debug = True
        elif opt in ('-h', '--help'):
            print_clo_help()
            sys.exit(os.EX_OK)
        elif opt in ('-n', '--nofork'):
            var.fork = False
        elif opt in ('-p', '--pdebug'):
            import pdb
            pdb.set_trace()

    signal.signal(signal.SIGINT, on_sigint)
    signal.signal(signal.SIGTERM, on_sigterm)
    signal.signal(signal.SIGHUP, on_sighup)
    signal.signal(signal.SIGPIPE, signal.SIG_IGN)
    signal.signal(signal.SIGALRM, signal.SIG_IGN)
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    signal.signal(signal.SIGWINCH, signal.SIG_IGN)
    signal.signal(signal.SIGTTIN, signal.SIG_IGN)
    signal.signal(signal.SIGTTOU, signal.SIG_IGN)
    signal.signal(signal.SIGTSTP, signal.SIG_IGN)

    print 'synarere -- a trivial, highly modular IRC bot.'
    print 'Copyright (C) 2010 Michael Rodriguez.'
    print 'Rights to this code are documented in docs/LICENSE.\n'

    # Initialize the configuration parser.
    try:
        instance.conf = configparser.ConfigParser(var.config_file)
    except configparser.Exception, errstr:
        print 'A fatal exception occurred while initializing the configuration parser: %s: %s.' % (var.config_file, errstr)
        sys.exit(os.EX_CONFIG)

    # Check to see if we're already running.
    try:
        pid_file = open(instance.conf.get('options', 'pidfile')[0], 'r')

        try:
            pid = pid_file.read()

            if pid:
                pid = int(pid)

                try:
                    os.kill(pid, 0)
                except OSError:
                    pass

                else:
                    print >> sys.stderr, 'An instance of synarere is already running.'
                    sys.exit(os.EX_SOFTWARE)
        finally:
            pid_file.close()
    except IOError:
        pass

    # Fork into the background.
    if var.fork:
        try:
            pid = os.fork()
        except OSError, e:
            return (e.errno, e.strerror)

        # This is the child process.
        if pid == 0:
            os.setsid()

            # Now the child fork()'s a child in order to prevent
            # acquisition of a controlling terminal.
            try:
                pid = os.fork()
            except OSError, e:
                return (e.errno, e.strerror)

            # This is the second child process.
            if pid == 0:
                os.chdir(os.getcwd())
                os.umask(0)

            # This is the first child.
            else:
                print 'PID:', pid
                print 'Running in background mode from:', os.getcwd()
                os._exit(0)
        else:
             os._exit(0)

        # Try to write the pid file.
        try:
            pid_file = open(instance.conf.get('options', 'pidfile')[0], 'w')
            pid_file.write(str(os.getpid()))
            pid_file.close()
        except IOError, e:
            print >> sys.stderr, 'Unable to write PID to %s: %s.' % (os.getpid(), os.strerror(e.args[0]))

        # Try to close all open file descriptors.
        # If we can't find the max number, just close
        # the first 256.
        try:            
            maxfd = os.sysconf('SC_OPEN_MAX')
        except (AttributeError, ValueError):
            maxfd = 256
        
        for fd in range(0, maxfd):
            try:                
                os.close(fd)    
            except OSError:
                pass
            
        # Redirect the standard file descriptors to /dev/null.
        os.open('/dev/null', os.O_RDONLY)                     
        os.open('/dev/null', os.O_RDWR)  
        os.open('/dev/null', os.O_RDWR)
    else:
        print 'PID:', os.getpid()
        print 'Running in foreground mode from: %s\n' % os.getcwd()

    # Initialize the logger.
    logger.init()

    # Load all modules listed in the configuration.
    modules.load_all_from_conf()

    # Connect to all IRC networks.
    irc.connect_to_all()

    # Start the loop.
    while True:
        # We do not want to poll on no connections, because this makes CPU
        # usage spike. Instead, just sleep.
        if len(var.conns) < 0:
            time.sleep(1)
        else:
            asyncore.poll(1)

    # This should NEVER happen.
    instance.exit('Main loop exited.', os.EX_SOFTWARE)

if __name__ == '__main__':
    main(sys.argv[1:])
