from optparse import make_option

import os
import re
import shutil
import subprocess # since python 2.4
import sys
from dojango.conf import settings

try:
    from django.core.management.base import BaseCommand, CommandError
except ImportError:
    # Fake BaseCommand out so imports on django 0.96 don't fail.
    BaseCommand = object
    class CommandError(Exception):
        pass

class Command(BaseCommand):
    '''This command is used to create your own dojo build. To start a build, you just
    have to type:
    
       ./manage.py dojobuild
    
    in your django project path. With this call, the default build profile "dojango" is used 
    and dojango.profile.js will act as its dojo build configuration. You can also add the 
    option --build_version=dev1.1.1 (for example) to mark the build with it.
    If you want to call a specific build profile from DOJO_BUILD_PROFILES, you just have to 
    append the profile name to this commandline call:
    
       ./manage.py dojobuild profilename
    
    '''
    
    option_list = BaseCommand.option_list + (
        make_option('--build_version', dest='build_version',
            help='Set the version of the build release (e.g. dojango_1.1.1).'),
        make_option('--minify', dest='minify', action="store_true", default=False,
            help='Does a dojo mini build (mainly removing unneeded files (tests/templates/...)'),
        make_option('--minify_extreme', dest='minify_extreme', action="store_true", default=False,
            help='Does a dojo extreme-mini build (keeps only what is defined in build profile and all media files)'),
        make_option('--prepare_zipserve', dest='prepare_zipserve', action="store_true", default=False,
            help='Zips everything you have built, so it can be deployed to Google AppEngine'),
    )
    help = "Builds a dojo release."
    args = '[dojo build profile name]'
    dojo_base_dir = None
    dojo_release_dir = None
    skip_files = None
    
    def handle(self, *args, **options):
        if len(args)==0:
            # with no param, we use the default profile, that is defined in the settings
            profile_name = settings.DOJO_BUILD_PROFILE
        else:
            profile_name = args[0]
        profile = self._get_profile(profile_name)
        used_src_version = profile['used_src_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
        # used by minify_extreme!
        self.skip_files = profile.get("minify_extreme_skip_files", ())
        self.dojo_base_dir = "%(dojo_root)s/%(version)s" % \
                             {'dojo_root':settings.BASE_DOJO_ROOT, 
                             'version':used_src_version}
        # does the defined dojo-directory exist?
        util_base_dir = "%(dojo_base_dir)s/util" % {'dojo_base_dir':self.dojo_base_dir}
        if not os.path.exists(util_base_dir):
            raise CommandError('Put the the dojo source files (version \'%(version)s\') in the folder \'%(folder)s/%(version)s\' or set a different version in settings.DOJANGO_DOJO_BUILD_VERSION' % \
                               {'version':used_src_version,
                                'folder':settings.BASE_DOJO_ROOT})
        # check, if java is installed
        try:
            # ignoring output of the java call
            subprocess.call(settings.DOJO_BUILD_JAVA_EXEC, stdout=subprocess.PIPE) # will work with python >= 2.4
        except:
            raise CommandError('Please install java. You need it for building dojo.')
        buildscript_dir = os.path.abspath('%s/buildscripts' % util_base_dir)
        if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
            executable = '%(java_exec)s -jar ../shrinksafe/custom_rhino.jar build.js' % \
                         {'java_exec':settings.DOJO_BUILD_JAVA_EXEC}
        else:
            # use the new build command line call!
            if(os.path.sep == "\\"):
                executable = 'build.bat'
            else:
                executable = './build.sh'
                # force executable rights!
                os.chmod(os.path.join(buildscript_dir, 'build.sh'), 0755)
        # use the passed version for building
        version = options.get('build_version', None)
        if not version:
            # if no option --build_version was passed, we use the default build version
            version = profile['build_version'] % {'DOJO_BUILD_VERSION': settings.DOJO_BUILD_VERSION} # no dependencies to project's settings.py file!
        # we add the version to our destination base path
        self.dojo_release_dir = '%(base_path)s/%(version)s' % {
                          'base_path':profile['base_root'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT},
                          'version':version} # we don't want to have a dependancy to the project's settings file!
        release_dir = os.path.abspath(os.path.join(self.dojo_release_dir, "../"))
        # the build command handling is so different between the versions!
        # sometimes we need to add /, sometimes not :-(
        if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
            release_dir = release_dir + os.path.sep
        # setting up the build command
        build_addons = ""
        if settings.DOJO_BUILD_USED_VERSION >= '1.2.0':
            # since version 1.2.0 there is an additional commandline option that does the mini build (solved within js!)
            build_addons = "mini=true"
        exe_command = 'cd %(buildscript_dir)s && %(executable)s version=%(version)s releaseName="%(version)s" releaseDir="%(release_dir)s" %(options)s %(build_addons)s' % \
                      {'buildscript_dir':buildscript_dir,
                       'executable':executable,
                       'version':version,
                       'release_dir':release_dir,
                       'options':profile['options'] % {'BASE_MEDIA_ROOT':settings.BASE_MEDIA_ROOT},
                       'build_addons':build_addons}
        # print exe_command
        minify = options['minify']
        minify_extreme = options['minify_extreme']
        prepare_zipserve = options['prepare_zipserve']
        if settings.DOJO_BUILD_USED_VERSION < '1.2.0' and (minify or minify_extreme):
            self._dojo_mini_before_build()
        if sys.platform == 'win32': # fixing issue #39, if dojango is installed on a different drive
            exe_command = os.path.splitdrive(buildscript_dir)[0] + ' && ' + exe_command
        
        # do the build
        os.system(exe_command)
        if settings.DOJO_BUILD_USED_VERSION < '1.2.0':
            if minify or minify_extreme:
                self._dojo_mini_after_build()
        if minify_extreme:
            self._dojo_mini_extreme()
        if prepare_zipserve:
            self._dojo_prepare_zipserve()
        
    def _get_profile(self, name):
        default_profile_settings = settings.DOJO_BUILD_PROFILES_DEFAULT
        try:
            profile = settings.DOJO_BUILD_PROFILES[name]
            # mixing in the default settings for the build profiles!
            default_profile_settings.update(profile)
            return default_profile_settings
        except KeyError:
            raise CommandError('The profile \'%s\' does not exist in DOJO_BUILD_PROFILES' % name)
        
    def _dojo_mini_before_build(self):
        # FIXME: refs #6616 - could be able to set a global copyright file and null out build_release.txt
        shutil.move("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir)
        if not os.path.exists("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir):
            f = open("%s/util/buildscripts/copyright.txt" % self.dojo_base_dir, 'w')
            f.write('''/*
Copyright (c) 2004-2008, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/''')
            f.close()
        else:
            shutil.copyfile("%s/util/buildscripts/copyright_mini.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
        shutil.move("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir)
        # create an empty build-notice-file
        f = open("%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir, 'w')
        f.close()
    
    def _dojo_mini_after_build(self):
        try: 
            '''Copied from the build_mini.sh shell script (thank you Pete Higgins :-))'''
            if not os.path.exists(self.dojo_release_dir):
                raise CommandError('The dojo build failed! Check messages above!')
            else:
                # remove dojox tests and demos - they all follow this convetion
                self._remove_files('%s/dojox' % self.dojo_release_dir, ('^tests$', '^demos$'))
                # removed dijit tests
                dijit_tests = ("dijit/tests", "dijit/demos", "dijit/bench", 
                               "dojo/tests", "util",
                               "dijit/themes/themeTesterImages")
                self._remove_folders(dijit_tests)
                # noir isn't worth including yet
                noir_theme_path = ("%s/dijit/themes/noir" % self.dojo_release_dir,)
                self._remove_folders(noir_theme_path)
                # so the themes are there, lets assume that, piggyback on noir: FIXME later
                self._remove_files('%s/dijit/themes' % self.dojo_release_dir, ('^.*\.html$',))
                self._remove_files(self.dojo_release_dir, ('^.*\.uncompressed\.js$',))
                # WARNING: templates have been inlined into the .js -- if you are using dynamic templates,
                # or other build trickery, these lines might not work!
                self._remove_files("dijit/templates", ("^\.html$",))
                self._remove_files("dijit/form/templates", ("^\.html$",))
                self._remove_files("dijit/layout/templates", ("^\.html$",))
                # .. assume you didn't, and clean up all the README's (leaving LICENSE, mind you)
                self._remove_files('%s/dojo/dojox' % self.dojo_release_dir, ('^README$',))
                dojo_folders = ("dojo/_base",)
                self._remove_folders(dojo_folders)
                os.remove("%s/dojo/_base.js" % self.dojo_release_dir)
                os.remove("%s/dojo/build.txt" % self.dojo_release_dir)
                os.remove("%s/dojo/tests.js" % self.dojo_release_dir)
        except Exception, e:
            print e
        # cleanup from above, refs #6616
        shutil.move("%s/util/buildscripts/_copyright.txt" % self.dojo_base_dir, "%s/util/buildscripts/copyright.txt" % self.dojo_base_dir)
        shutil.move("%s/util/buildscripts/_build_notice.txt" % self.dojo_base_dir, "%s/util/buildscripts/build_notice.txt" % self.dojo_base_dir)
        
    def _remove_folders(self, folders):
        for folder in folders:
            if os.path.exists("%s/%s" % (self.dojo_release_dir, folder)):
                shutil.rmtree("%s/%s" % (self.dojo_release_dir, folder))
            
    def _remove_files(self, base_folder, regexp_list):
        for root, dirs, files in os.walk(base_folder):
            for file in files:
                # remove all html-files
                for regexp in regexp_list:
                    my_re = re.compile(regexp)
                    if my_re.match(file):
                        os.remove(os.path.join(root, file))
            for dir in dirs:
                for regexp in regexp_list:
                    my_re = re.compile(regexp)
                    if my_re.match(dir):
                        shutil.rmtree(os.path.join(root, dir))
    
    SKIP_FILES = (
        '(.*\.png)',
        '(.*\.gif)',
        '(.*\.jpg)',
        '(.*\.svg)',
        '(.*\.swf)',
        '(.*\.fla)',
        '(.*\.mov)',
        '(.*\.smd)',
        '(dojo/_firebug/firebug\..*)',
        '(dojo/dojo\.(xd\.)?js)',
        '(dojo/nls/.*)',
        '(dojo/resources/dojo\.css)',
        '(dojo/resources/blank\.html)',
        '(dojo/resources/iframe_history\.html)',
        '(dijit/themes/tundra/tundra\.css)',
        '(dijit/themes/soria/soria\.css)',
        '(dijit/themes/nihilo/nihilo\.css)',
        '(dojox/dtl/contrib/.*)',
        '(dojox/dtl/ext-dojo/.*)',
        '(dojox/dtl/filter/.*)',
        '(dojox/dtl/render/.*)',
        '(dojox/dtl/tag/.*)',
        '(dojox/dtl/utils/.*)',
        '(dojox/io/proxy/xip_.*\.html)',
    )
    def _dojo_mini_extreme(self):
        """
        This method removes all js files and just leaves all layer dojo files and static files (like "png", "gif", "svg", "swf", ...)
        """
        # prepare the regexp of files not to be removed!
        # mixin the profile specific skip files
        skip_files = self.SKIP_FILES + self.skip_files
        my_re = re.compile('^(.*/)?(%s)$' % "|".join(skip_files))
        try:
            '''Copied from the build_mini.sh shell script'''
            if not os.path.exists(self.dojo_release_dir):
                raise CommandError('The dojo build failed! Check messages above!')
            else:
                for root, dirs, files in os.walk(self.dojo_release_dir):
                    for file in files:
                        # remove all html-files
                        my_file = os.path.abspath(os.path.join(root, file))
                        if not my_re.match(my_file):
                            os.remove(my_file)
                # now remove all empty directories
                for root, dirs, files in os.walk(self.dojo_release_dir):
                    for dir in dirs:
                        try:
                            # just empty directories will be removed!
                            os.removedirs(os.path.join(root, dir))
                        except OSError:
                            pass
        except Exception, e:
            print e

    DOJO_ZIP_SPECIAL = {'dojox': ['form', 'widget', 'grid']} # these modules will be zipped separately
    def _dojo_prepare_zipserve(self):
        """
        Creates zip packages for each dojo module within the current release folder.
        It splits the module dojox into several modules, so it fits the 1000 files limit of
        Google AppEngine.
        """
        for folder in os.listdir(self.dojo_release_dir):
            module_dir = '%s/%s' % (self.dojo_release_dir, folder)
            if os.path.isdir(module_dir):
                if folder in self.DOJO_ZIP_SPECIAL.keys():
                    for special_module in self.DOJO_ZIP_SPECIAL[folder]:
                        special_module_dir = os.path.join(module_dir, special_module)
                        create_zip(special_module_dir, 
                                   '%(base_module)s/%(special_module)s' % {
                                       'base_module': folder,
                                       'special_module': special_module
                                   },
                                   '%(module_dir)s.%(special_module)s.zip' % {
                                       'module_dir': module_dir,
                                       'special_module': special_module
                                   }
                        )
                        # remove the whole special module
                        shutil.rmtree(special_module_dir)
                # now add the 
                create_zip(module_dir, folder, module_dir + ".zip")
                shutil.rmtree(module_dir)
                        

def zipfolder(path, relname, archive):
    paths = os.listdir(path)
    for p in paths:
        p1 = os.path.join(path, p) 
        p2 = os.path.join(relname, p)
        if os.path.isdir(p1): 
            zipfolder(p1, p2, archive)
        else:
            archive.write(p1, p2) 

def create_zip(path, relname, archname):
    import zipfile
    archive = zipfile.ZipFile(archname, "w", zipfile.ZIP_DEFLATED)
    if os.path.isdir(path):
        zipfolder(path, relname, archive)
    else:
        archive.write(path, relname)
    archive.close()
