#!python

# This Source Code is subject to the terms of the Mozilla Public License
# version 2.0 (the "License"). You can obtain a copy of the License at
# http://mozilla.org/MPL/2.0/.

import errno
import inspect
import os
import shutil
import socket
import sys
import tempita
import templeton
try:
    import virtualenv
except ImportError:
    pass

TEMPLETOND_SOCK_FILENAME = '/var/run/templetond/templetond.sock'

data_dir = os.path.dirname(os.path.abspath(templeton.__file__))


class Commands(object):

    def handle_cmd(self, args):
        cmd = args[0]
        try:
            f = getattr(self, 'cmd_' + cmd)
        except AttributeError:
            sys.stderr.write('No such command "%s".\n' % cmd)
            return errno.EOPNOTSUPP
        if not callable(f):
            sys.stderr.write('No such command "%s".\n' % cmd)
            return errno.EOPNOTSUPP
        try:
            rc = f(*args[1:])
        except TypeError:
            sys.stderr.write('Invalid arguments.\n')
            return errno.EINVAL
        return rc

    def help(self):
        """Help on available commands."""
        help = ''
        cmds = [(x, y) for x, y in Commands.__dict__.iteritems()]
        cmds.sort(key=lambda x: x[0])
        for name, member in cmds:
            if name.startswith('cmd_') and callable(member):
                help += '    %s\n' % ' '.join([name[4:]] +
                                              ['<%s>' % x for x in
                                               inspect.getargspec(member).args[1:]])
                if member.__doc__:
                    help += '        %s\n' % member.__doc__.splitlines()[0]
        return 'Available commands:\n%s' % help

    def socket_command_with_project(self, cmd_str, app_name):
        if app_name:
            arg = 'name:%s' % app_name
            verify_path = ''
        else:
            arg = 'path:%s' % self.app_path
            verify_path = self.app_path
        self.socket_command('%s %s' % (cmd_str, arg), verify_path)

    def socket_command(self, cmd_str, verify_path=''):
        """If verify_path is given, verify that it is a templeton path."""
        if verify_path and \
           not os.path.exists(os.path.join(verify_path, 'server',
                                           'server.py')) and \
           not os.path.exists(os.path.join(verify_path, 'src',
                                           os.path.basename(os.getcwd()),
                                           'server', 'server.py')):
            sys.stderr.write('This does not appear to be a templeton app.\n')
            return errno.ENOENT
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        try:
            sock.connect(TEMPLETOND_SOCK_FILENAME)
            sock.send(cmd_str + '\n')
        except socket.error, e:
            if e.errno == errno.ENOENT or e.errno == errno.ENOTCONN or \
               e.errno == errno.ECONNREFUSED:
                sys.stderr.write('Could not connect to socket; is templetond running?\n')
                return e.errno
            
        rsp = ''
        while '\n' not in rsp:
            read = sock.recv(1024)
            if read:
                rsp += read
            else:
                break
        sock.close()
        err = 0
        errstr = ''
        rsp = rsp.rstrip('\n')
        if rsp[:5] == 'ERROR':
            s = rsp.split(' ')
            try:
                err = int(s[1])
            except ValueError:
                err = 255
                errstr = rsp
            else:
                errstr = ' '.join(s[2:])
        elif rsp[:2] == 'OK':
            if len(rsp) > 2:
                print rsp[2:].strip()
        else:
            err = 255
            errstr = rsp
        if errstr:
            sys.stderr.write('error: %s\n' % errstr)
        return err

    def cmd_init(self, appname):
        """Create a new templeton project.
        Creates the directory structure from stored templates. Also creates
        a virtualenv, if module is available.

        FIXME: Probably want to be able to force nonvirtualenv...?
        """
        if '/' in appname:
            print 'Slashes are not allowed in project names.'
            return errno.EINVAL
        if os.path.exists(appname):
            print 'Cannot initialize project "%s" over existing %s.' % \
                (appname, 'directory' if os.path.isdir(appname) else 'file')
            return errno.EEXIST
        templates_dir = os.path.join(data_dir, 'templates', 'project')
        if 'virtualenv' in sys.modules:
            print 'Creating virtualenv...'
            virtualenv.create_environment(appname)
            os.mkdir(os.path.join(appname, 'src'))
            appdir = os.path.join(appname, 'src', appname)
        else:
            appdir = appname
        print 'Creating templeton app...'
        shutil.copytree(templates_dir, appdir)
        # special templatization for index.html
        html_dir = os.path.join(appdir, 'html')
        tmpl = tempita.Template.from_filename(os.path.join(html_dir,
                                                           'index.html.tmpl'))
        f = file(os.path.join(html_dir, 'index.html'), 'w')
        f.write(tmpl.substitute(appname=appname))
        f.close()
        os.unlink(os.path.join(html_dir, 'index.html.tmpl'))
        return 0

    def cmd_install(self, wwwdata):
        """Install the templeton static library to a web server root."""
        server_dir = os.path.join(data_dir, 'server')
        shutil.copytree(server_dir, os.path.join(wwwdata, 'templeton'))
        return 0

    def cmd_register(self, app_path=None):
        """Registers a new templeton project with the app server.
        Path defaults to current path.
        """
        if app_path:
            app_path = os.path.abspath(app_path)
        else:
            app_path = os.getcwd()
        app_name = os.path.basename(app_path)
        rc = self.socket_command('register %s %s' % (app_name, app_path))
        return rc

    def cmd_unregister(self, app_name=None):
        """Unregisters a templeton project, removing it from the app server."""
        rc = self.socket_command_with_project('unregister', app_name)
        return rc

    def cmd_enable(self, app_name=None):
        """Enables a templeton project if previously disabled."""
        rc = self.socket_command_with_project('enable', app_name)
        return rc

    def cmd_disable(self, app_name=None):
        """Disables a templeton project."""
        rc = self.socket_command_with_project('disable', app_name)
        return rc

    def cmd_start(self, app_name=None):
        """Starts the project's FastCGI server."""
        rc = self.socket_command_with_project('start', app_name)
        return rc

    def cmd_stop(self, app_name=None):
        """Stops the project's FastCGI server."""
        rc = self.socket_command_with_project('stop', app_name)
        return rc

    def cmd_restart(self, app_name=None):
        """Restarts the project's FastCGI server."""
        rc = self.socket_command_with_project('restart', app_name)
        return rc

    def cmd_list(self):
        """Lists all templeton projects registered with the server."""
        rc = self.socket_command('list', False)
        return rc


if __name__ == '__main__':
    from optparse import OptionParser

    cmds = Commands()

    usage = '''%prog [options] <cmd>

'''
    usage += cmds.help()

    parser = OptionParser(usage=usage)
    (options, args) = parser.parse_args()
    if len(args) < 1:
        parser.print_help()
        sys.exit(errno.EINVAL)

    rc = cmds.handle_cmd(args)
    if rc == errno.EINVAL or rc == errno.EOPNOTSUPP:
        parser.print_help()
    sys.exit(rc)
