#!/usr/bin/python

#
#  erlenmeyer
#  Erlenmeyer
#
#  Created by Patrick Perini on February 5, 2013.
#  See LICENSE.txt for licensing information.
#

# imports
import os
import flask
import shutil
import jinja2
import getpass
import optparse
import datetime
import erlenmeyer
from erlenmeyer import CoreData
from erlenmeyer.ext import jinja2 as jinja2Extensions
from sqlalchemy import exc as sqlExceptions
from flask.ext.sqlalchemy import SQLAlchemy

# constants
projectFiles = {
    "flaskProject": "%(directory)s/%(projectName)s.py",
    
    "modelsInit": "%(directory)s/models/__init__.py",
    "modelsModelObject": "%(directory)s/models/%(modelName)s.py",
    
    "handlersInit": "%(directory)s/handlers/__init__.py",
    "handlersModelObjectHandler": "%(directory)s/handlers/%(modelName)sHandler.py",
    
    "flaskProjectDocumentation": "%(directory)s/documentation/%(projectName)s.html",
    
    "settings": "%(directory)s/settings/settings.json"
}

templateFiles = {
    "flaskProject": "erlenmeyer.project.tmpl.py",
    
    "modelsModel": "erlenmeyer.model.tmpl.py",
    "modelsModelObject": "erlenmeyer.ModelObject.tmpl.py",
    
    "handlersModelObjectHandler": "erlenmeyer.ModelObjectHandler.tmpl.py",
    
    "flaskProjectDocumentation": "erlenmeyer.server.tmpl.html",
    
    "settings": "erlenmeyer.settings.tmpl.json"
}

# globals
optionParser = optparse.OptionParser()

templateEnvironment = jinja2.Environment(
    loader = jinja2.FileSystemLoader('/usr/local/bin')
)

for filter in dir(jinja2Extensions):
    templateEnvironment.filters[filter] = getattr(jinja2Extensions, filter)

# functions
def main():
    (options, args) = optionParser.parse_args()
    
    commandIsMalformed = False
    commandIsMalformed |= (not options.project)
    commandIsMalformed |= (not options.coreDataFile)
    
    if commandIsMalformed:
        optionParser.print_help()
        return
        
    # setup variables
    project = os.path.abspath(options.project)
    coreDataFile = os.path.abspath(options.coreDataFile)
    sqlLogin = {
        "user": options.sqlLogin.split(":")[0],
        "password": options.sqlLogin.split(":")[1],
    }
        
    # trap errors
    if not options.forceProjectCreation and os.path.isdir(project):
        print "Error: %(project)s already exists." % ({
            "project": project
        })
        
        return
        
    if not os.path.isdir(coreDataFile):
        print "Error: %(coreDataFile)s does not exist." % ({
            "coreDataFile": coreDataFile
        })
        
        return
        
    # setup variables
    if options.forceProjectCreation and os.path.isdir(project):
        shutil.rmtree(project)
        
    projectName = os.path.basename(project)
    sqlLogin['database'] = projectName.lower()
    
    metadata = {
        "projectName": projectName,
        "projectOwner": projectOwner if options.projectOwner else getpass.getuser(),
        "fileAuthor": options.fileAuthor if options.fileAuthor else getpass.getuser(),
        "pubDate": datetime.datetime.today().strftime("%h %d, %Y"),
        "pubYear": datetime.datetime.today().strftime("%Y")
    }
    
    # retrieve models
    print "Parsing Core Data file..."
    coreData = CoreData.CoreData(coreDataFile, options.primaryKey)
    
    # create flask file
    print "Creating Flask file..."
    flaskProjectTemplateDict = {
        "metadata": metadata,
        "models": coreData['models']
    }
    
    flaskProjectFileNameDict = {
        "directory": project,
        "projectName": projectName
    }
    
    renderTemplate('flaskProject', flaskProjectTemplateDict, flaskProjectFileNameDict)
    
    # create models files
    # - create init file
    print "Creating models module..."
    modelsInitFileNameDict = {
        "directory": project
    }
    
    renderTemplate('modelsInit', {}, modelsInitFileNameDict)
    
    # - create Model subclass files
    for model in coreData['models']:
        print "  Creating %(model)s object..." % ({
            "model": model["className"]
        })
        
        modelsModelObjectTemplateDict = {
            "metadata": metadata,
            "model": model
        }
        
        modelsModelFileNameDict = {
            "directory": project,
            "modelName": model["className"]
        }
        
        renderTemplate('modelsModelObject', modelsModelObjectTemplateDict, modelsModelFileNameDict)

    # create handlers files
    # - create init file
    print "Creating handlers module..."
    
    handlersInitFileNameDict = {
        "directory": project
    }
    
    renderTemplate('handlersInit', {}, modelsInitFileNameDict)
    
    # - create Model subclass files
    for model in coreData['models']:
        print "  Creating %(model)s handler..." % ({
            "model": model["className"]
        })
        
        handlersModelObjectHandlerTemplateDict = {
            "metadata": metadata,
            "model": model
        }
        
        handlersModelHandlerFileNameDict = {
            "directory": project,
            "modelName": model["className"]
        }
        
        renderTemplate('handlersModelObjectHandler', handlersModelObjectHandlerTemplateDict, handlersModelHandlerFileNameDict)
        
    # create documentation directory
    # - create documentation file
    print "Creating documentation file..."
    
    documentationTemplateDict = {
        "metadata": metadata,
        "models": coreData['models']
    }
    
    documentationTemplateFileNameDict = {
        "directory": project,
        "projectName": projectName
    }
    
    renderTemplate('flaskProjectDocumentation', documentationTemplateDict, documentationTemplateFileNameDict)
        
    # create settings directory
    # - create settings file
    print "Creating settings file..."
    
    settingsTemplateDict = {
        "metadata": metadata,
        "sql": sqlLogin
    }
    
    settingsFileNameDict = {
        "directory": project
    }
    
    renderTemplate('settings', settingsTemplateDict, settingsFileNameDict)
        
    # create MySQL database & tables
    print "Creating MySQL database..."
    mySQLServer = 'mysql://%(user)s:%(password)s@localhost' % (sqlLogin)
    
    flaskApp = flask.Flask(__name__)
    flaskApp.config['SQLALCHEMY_DATABASE_URI'] = '%(server)s/%(database)s' % ({
        "server": mySQLServer,
        "database": sqlLogin['database']
    })
    
    database = SQLAlchemy(flaskApp)
    
    databaseEngine = database.create_engine(mySQLServer)
    databaseEngineExecutor = databaseEngine.connect().execute
    
    try:
        databaseEngineExecutor("create database %(database)s" % ({
            "database": sqlLogin['database']
        }))
    except sqlExceptions.ProgrammingError:
        print "  MySQL database already exists!"
    
    # finish
    print "Finished!"
        
# template renderers
def renderTemplate(fileName, templateDict, projectFileNameDict):
    """
    Renders the given template and writes it to the cooresponding file.
    
    @param fileName: The name of the template and output file.
    @param templateDict: The values for the template.
    @param projectFileNameDict: The values for the project file name.
    """
    
    templateFilePath = projectFiles[fileName] % (projectFileNameDict)
    templatePath = os.path.dirname(templateFilePath)
    
    template = None
    if fileName in templateFiles:
        template = templateEnvironment.get_template(templateFiles[fileName])
        
    if 'metadata' in templateDict:
        templateDict['metadata']['fileName'] = os.path.basename(templateFilePath)
        
    renderedTemplate = template.render(templateDict) if template else ""
    
    if not os.path.isdir(templatePath):
        os.makedirs(templatePath)
        
    templateFile = open(templateFilePath, 'w')
    templateFile.write(renderedTemplate)
    templateFile.close()        

# main
if __name__ == "__main__":
    # add options
    optionParser.add_option(
        "-p", "--project", dest = "project",
        help = "Location of the project to manipulate."
    )
    
    optionParser.add_option(
        "-c", "--coredata", dest = "coreDataFile",
        help = "Location of the Core Data xcdatamodeld file."
    )
    
    optionParser.add_option(
        "--sql", dest = "sqlLogin",
        help = "The username:password combination for SQLALchemy.",
        default = "root:"
    )
    
    optionParser.add_option(
        "--primaryKey", dest = "primaryKey",
        default = "uuid", help = "The primary key of all Models. Default to UUID."
    )
    
    optionParser.add_option(
        "--projectOwner", dest = "projectOwner",
        help = "The owner to whom this project is copyrighted.",
        default = None
    )
    
    optionParser.add_option(
        "--fileAuthor", dest = "fileAuthor",
        help = "The creator of this file. Defaults to current user.",
        default = None
    )
    
    optionParser.add_option(
        "--force", dest = "forceProjectCreation",
        help = "If the project already exists, delete it.",
        default = False, action = "store_true"
    )
    
    main()