#!/usr/bin/python

import os.path, sys, shutil, uuid, datetime, getpass
import extractor

# if script is part of installed package, get default template from configuration
try: 
    import xrtemplate
    defaultdata = xrtemplate.data_path
# otherwise, default template will be template.tar.gz in current directory
except ImportError:
    defaultdata="template.tar.gz"

# used to distinguish what needs editing in the template
templReserved = "appname"
vendorReserved = "vendor"

def quit(str, errno=1):
    sys.stderr.write(str)
    sys.stderr.write("\n")
    sys.exit(errno)

class Error(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return str(self.value)

class Project:
    def __init__(self, name, options):
        if not os.path.exists(options.destdir):
            raise Error("%s does not exist", options.destdir)

        self.name = name
        self.options = options
        if sys.platform == "darwin":
            self.projectpath = os.path.join(options.destdir, "%s.app" % name, "Contents", "Resources")
        else:
            self.projectpath = os.path.join(options.destdir, name)

        self.datafile = options.datafile
        self.vendor = options.vendor
        self.version = options.version
        if not self.validname():
            raise Error ("%s is not a valid name. Project name must begin with"
            " a letter and contain only letters, numbers or underscore." %
            (name))
        if self.name.lower() == templReserved.lower():
            raise Error ("%s is not a valid name. Project name cannot be %s" % (self.name, self.name))
        if self.name.lower() == vendorReserved.lower():
            raise Error ("%s is not a valid name. Project name cannot be %s" % (self.name, self.name))
        if os.path.exists(self.projectpath):
            raise Error ("A file or directory named %s already exist in %s" % (self.name, self.options.destdir))

        os.makedirs(self.projectpath)
        self.extractor = extractor.factory(self.datafile, self.projectpath)

    def darwinAppBundle(self):
        import shutil
        info_plist = os.path.join(self.projectpath, "appres", "MacOSX", "Info.plist")
        if not os.path.exists(info_plist):
            return
        appdir = os.path.join(self.options.destdir, "%s.app" % self.name)
        contents = os.path.join(appdir, "Contents")
        shutil.move(info_plist, contents)
        bindir = os.path.join(contents, "MacOS")
        os.mkdir(bindir)
        bin = "/"+os.path.join("Library", "Frameworks", "XUL.framework", "Versions", "Current", "xulrunner")
        shutil.copy(bin, os.path.join(bindir, "xulrunner"))

    def install(self):
        self.extract()
        if not os.path.isfile(os.path.join(self.projectpath, "application.ini")):
            raise Error ("template has no application.ini. Aborting")
        self.edtemplate()
        if sys.platform == "darwin":
            self.darwinAppBundle()

    def extract(self):
        self.extractor.extract()

    def interpolateini(self, inipath, data):
        fr = open(inipath, 'rb')
        lines = []
        for line in fr.readlines():
            for (key, value) in data.items():
                if line.rstrip() == ("%s=" % key):
                    line = "%s=%s" % (key, value) + os.linesep
            lines.append(line)
        fr.close()
        fw = open(inipath, 'wb')
        for line in lines:
            fw.write(line)
        fw.close()


    def edtemplate(self):

        inipath = os.path.join(self.projectpath, "application.ini")

        date = datetime.date.today()
        # adds ID, BuildID, Vendor and Version to application.ini if they are
        # not already defined
        data = {
            "ID": "{%s}" % (uuid.uuid4().urn[9:]),
            "BuildID": "%d%02d%02d" % (date.year, date.month, date.day),
            "Name": self.name,
            "Vendor": self.vendor,
            "Version": self.version
        }
        self.interpolateini(inipath, data)

        for (dirpath, dirnames, filenames) in os.walk(self.projectpath):
            for file in filenames:
                if self.name == os.path.splitext(file)[0]:
                    raise Error ("%s is not a valid name. Template contains a file whose name is %s,"
                                "and this would lead to potential conflicts when renaming files" 
                                % (self.name, self.name))
                # replaces $APPNAME occurences with current application name
                filepath = os.path.join(dirpath, file)
                fr = open(filepath, 'rb')
                lines = []
                appname = '$' + templReserved.upper()
                vendor = '$' + vendorReserved.upper()
                for line in fr.readlines():
                    line = line.replace(appname, self.name)
                    line = line.replace(vendor, self.vendor)
                    lines.append(line)
                fr.close()
                fw = open(filepath, 'wb')
                for line in lines:
                    fw.write(line)
                fw.close()
                splitname = os.path.splitext(file)
                if splitname[0] == templReserved:
                    newpath = os.path.join(dirpath, self.name + splitname[1])
                    os.rename(filepath, newpath)
                
    def cancel(self):
        if os.path.isdir(self.projectpath):
            shutil.rmtree(self.projectpath)
    
    def validname(self):
        """ checks if name is a valid project name. A project name must contain only
        ascii letter (uppercase or lowercase), numbers, and underscore (_). Also,
        it must begin with a letter."""
        if len(self.name) == 0:
            return False
        def isvalidchar(char):
            return char.isalnum() or char == '_'
        def isvalidfirstchar(char):
            return char.isalpha()
        if not isvalidfirstchar(self.name[0]):
            return False
        for char in self.name:
            if not isvalidchar(char):
                return False
        return True

def main():
    from optparse import OptionParser
    usage = "Usage: %prog [options] projectname"
    parser = OptionParser(usage)
    parser.add_option("-c", "--creator", dest="vendor", default=getpass.getuser(),
            help=("use VENDOR instead of default %s" % (getpass.getuser())))
    parser.add_option("-v", "--version", dest="version", default="0.1",
            help=("use Version instead of default 0.1"))
    parser.add_option("-t", "--template", dest="datafile", default=defaultdata,
            help=("use DATAFILE instead of default template %s" % (defaultdata)))
    parser.add_option("-d", "--destination", dest="destdir", default=os.path.expanduser('~'),
            help=("extract template in DESTDIR instead of default %s" % (os.path.expanduser('~'))))
    (options, positional) = parser.parse_args()

    try:
        name = positional[0]
    except IndexError:
        quit("you must provide a project name")

    try:
        proj = Project(name, options)
    except Error, inst:
        quit (str(inst))
    try:
        proj.install()
    except Error, inst:
        proj.cancel()
        quit (str(inst))
    except:
        trace = sys.exc_info()
        proj.cancel()
        raise trace[0], trace[1]
    print ("%s created in %s" % (proj.name, proj.projectpath))

if __name__ == "__main__":
    main()
