#!/usr/bin/env python
__license__   = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' Create an OSX installer '''

import sys, re, os, shutil, subprocess, stat, glob, zipfile, plistlib
from setup import __version__ as VERSION, __appname__ as APPNAME, SRC, Command, \
        scripts, basenames, functions as main_functions, modules as main_modules
from setuptools import setup

try:
    from py2app.build_app import py2app
    from modulegraph.find_modules import find_modules
    py2app
except ImportError:
    py2app = object

PYTHON = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'

info = warn = None

class OSX32_Freeze(Command):

    def run(self, opts):
        global info, warn
        info, warn = self.info, self.warn
        main()


class BuildAPP(py2app):
    QT_PREFIX = '/Volumes/sw/qt'
    LOADER_TEMPLATE = \
r'''#!/usr/bin/env python
import os, sys, glob
path = os.path.abspath(os.path.realpath(__file__))
dirpath = os.path.dirname(path)
name = os.path.basename(path)
base_dir = os.path.dirname(os.path.dirname(dirpath))
resources_dir = os.path.join(base_dir, 'Resources')
frameworks_dir = os.path.join(base_dir, 'Frameworks')
extensions_dir = os.path.join(frameworks_dir, 'plugins')
r_dir = os.path.join(resources_dir, 'resources')
base_name = os.path.splitext(name)[0]
python = os.path.join(base_dir, 'MacOS', 'python')
qt_plugins = os.path.join(os.path.realpath(base_dir), 'MacOS')
loader_path = os.path.join(dirpath, base_name+'.py')
loader = open(loader_path, 'w')
site_packages = glob.glob(resources_dir+'/lib/python*/site-packages.zip')[0]
print >>loader, 'import sys'
print >>loader, 'sys.argv[0] =', repr(os.path.basename(path))
print >>loader, 'if', repr(dirpath), 'in sys.path: sys.path.remove(', repr(dirpath), ')'
print >>loader, 'sys.path.append(', repr(site_packages), ')'
print >>loader, 'sys.frozen = "macosx_app"'
print >>loader, 'sys.frameworks_dir =', repr(frameworks_dir)
print >>loader, 'sys.extensions_location =', repr(extensions_dir)
print >>loader, 'sys.resources_location =', repr(r_dir)
print >>loader, 'import os'
print >>loader, 'from %(module)s import %(function)s'
print >>loader, '%(function)s()'
loader.close()
os.chmod(loader_path, 0700)
os.environ['PYTHONHOME']        = resources_dir
os.environ['FONTCONFIG_PATH']   = os.path.join(resources_dir, 'fonts')
os.environ['MAGICK_HOME']       = os.path.join(frameworks_dir, 'ImageMagick')
os.environ['DYLD_LIBRARY_PATH'] = os.path.join(frameworks_dir, 'ImageMagick', 'lib')
os.environ['QT_PLUGIN_PATH']    = qt_plugins
args = [path, loader_path] + sys.argv[1:]
os.execv(python, args)
    '''

    def get_modulefinder(self):
        if self.debug_modulegraph:
            debug = 4
        else:
            debug = 0
        return find_modules(
            scripts=scripts['console'] + scripts['gui'],
            includes=list(self.includes) + main_modules['console'],
            packages=self.packages,
            excludes=self.excludes,
            debug=debug)

    @classmethod
    def makedmg(cls, d, volname,
                destdir='dist',
                internet_enable=True,
                format='UDBZ'):
        ''' Copy a directory d into a dmg named volname '''
        if not os.path.exists(destdir):
            os.makedirs(destdir)
        dmg = os.path.join(destdir, volname+'.dmg')
        if os.path.exists(dmg):
            os.unlink(dmg)
        subprocess.check_call(['/usr/bin/hdiutil', 'create', '-srcfolder', os.path.abspath(d),
                               '-volname', volname, '-format', format, dmg])
        if internet_enable:
           subprocess.check_call(['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg])
        return dmg

    @classmethod
    def qt_dependencies(cls, path):
        pipe = subprocess.Popen('/usr/bin/otool -L '+path, shell=True, stdout=subprocess.PIPE).stdout
        deps = []
        for l in pipe.readlines():
            match = re.search(r'(.*)\(', l)
            if not match:
                continue
            lib = match.group(1).strip()
            if lib.startswith(BuildAPP.QT_PREFIX):
                deps.append(lib)
        return deps

    @classmethod
    def fix_qt_dependencies(cls, path, deps):
        fp = '@executable_path/../Frameworks/'
        info('Fixing qt dependencies for:', os.path.basename(path))
        for dep in deps:
            match = re.search(r'(Qt\w+?)\.framework', dep)
            if not match:
                match = re.search(r'(phonon)\.framework', dep)
                if not match:
                    warn(dep)
                    raise Exception('Unknown Qt dependency')
            module = match.group(1)
            newpath = fp + '%s.framework/Versions/Current/%s'%(module, module)
            cmd = ' '.join(['/usr/bin/install_name_tool', '-change', dep, newpath, path])
            subprocess.check_call(cmd, shell=True)


    def add_qt_plugins(self):
        macos_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'MacOS')
        for root, dirs, files in os.walk(BuildAPP.QT_PREFIX+'/plugins'):
            for name in files:
                if name.endswith('.dylib'):
                    path = os.path.join(root, name)
                    dir = os.path.basename(root)
                    dest_dir = os.path.join(macos_dir, dir)
                    if not os.path.exists(dest_dir):
                        os.mkdir(dest_dir)
                    target = os.path.join(dest_dir, name)
                    shutil.copyfile(path, target)
                    shutil.copymode(path, target)
                    deps = BuildAPP.qt_dependencies(target)
                    BuildAPP.fix_qt_dependencies(target, deps)


        #deps = BuildAPP.qt_dependencies(path)

    def fix_python_dependencies(self, files):
        for f in files:
            subprocess.check_call(['/usr/bin/install_name_tool', '-change', '/Library/Frameworks/Python.framework/Versions/2.6/Python', '@executable_path/../Frameworks/Python.framework/Versions/2.6/Python', f])

    def fix_misc_dependencies(self, files):
        for path in files:
            frameworks_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'Frameworks')
            pipe = subprocess.Popen('/usr/bin/otool -L '+path, shell=True, stdout=subprocess.PIPE).stdout
            for l in pipe.readlines():
                match = re.search(r'\s+(.*?)\s+\(', l)
                if match:
                    dep = match.group(1)
                    name = os.path.basename(dep)
                    if not name:
                        name = dep
                    bundle = os.path.join(frameworks_dir, name)
                    if os.path.exists(bundle):
                        subprocess.check_call(['/usr/bin/install_name_tool', '-change', dep,
                                '@executable_path/../Frameworks/'+name, path])


    def add_plugins(self):
        self.add_qt_plugins()
        frameworks_dir = os.path.join(self.dist_dir, APPNAME + '.app', 'Contents', 'Frameworks')
        plugins_dir = os.path.join(frameworks_dir, 'plugins')
        if not os.path.exists(plugins_dir):
            os.mkdir(plugins_dir)

        maps = {}
        for f in glob.glob('src/calibre/plugins/*'):
            tgt = plugins_dir
            if f.endswith('.dylib'):
                tgt = frameworks_dir
            maps[f] = os.path.join(tgt, os.path.basename(f))
        deps = []
        for src, dst in maps.items():
            shutil.copyfile(src, dst)
            self.fix_qt_dependencies(dst, self.qt_dependencies(dst))
            deps.append(dst)
        self.fix_python_dependencies(deps)
        self.fix_misc_dependencies(deps)


    def run(self):
        py2app.run(self)
        resource_dir = os.path.join(self.dist_dir,
                                    APPNAME + '.app', 'Contents', 'Resources')
        frameworks_dir = os.path.join(os.path.dirname(resource_dir), 'Frameworks')
        all_scripts = scripts['console'] + scripts['gui']
        all_names   = basenames['console'] + basenames['gui']
        all_modules   = main_modules['console'] + main_modules['gui']
        all_functions = main_functions['console'] + main_functions['gui']

        info('\nAdding resources')
        dest = os.path.join(resource_dir, 'resources')
        if os.path.exists(dest):
            shutil.rmtree(dest)
        shutil.copytree(os.path.join(os.path.dirname(SRC), 'resources'), dest)

        info('\nAdding PoDoFo')
        pdf = glob.glob(os.path.expanduser('/Volumes/sw/podofo/libpodofo*.dylib'))[0]
        shutil.copyfile(pdf, os.path.join(frameworks_dir, os.path.basename(pdf)))

        info('\nAdding poppler')
        for x in ('pdftohtml', 'libpoppler.4.dylib', 'libpoppler-qt4.3.dylib'):
            tgt = os.path.join(frameworks_dir, x)
            os.link(os.path.join(os.path.expanduser('~/poppler'), x), tgt)
            self.fix_qt_dependencies(tgt, self.qt_dependencies(tgt))



        loader_path = os.path.join(resource_dir, 'loaders')
        if not os.path.exists(loader_path):
            os.mkdir(loader_path)
        for name, module, function in zip(all_names, all_modules, all_functions):
            path = os.path.join(loader_path, name)
            info('Creating loader:', path)
            f = open(path, 'w')
            f.write(BuildAPP.LOADER_TEMPLATE % dict(module=module,
                                                        function=function))
            f.close()
            os.chmod(path, stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH|stat.S_IREAD\
                     |stat.S_IWUSR|stat.S_IROTH|stat.S_IRGRP)


        info('Adding fontconfig')
        for f in glob.glob(os.path.expanduser('~/fontconfig-bundled/*')):
            dest = os.path.join(frameworks_dir, os.path.basename(f))
            if os.path.exists(dest):
                os.remove(dest)
            shutil.copyfile(f, dest)
        dst = os.path.join(resource_dir, 'fonts')
        if os.path.exists(dst):
            shutil.rmtree(dst)
        shutil.copytree('/usr/local/etc/fonts', dst, symlinks=False)

        self.add_plugins()


        info('Adding IPython')
        dst = os.path.join(resource_dir, 'lib', 'python2.6', 'IPython')
        if os.path.exists(dst): shutil.rmtree(dst)
        shutil.copytree(os.path.expanduser('~/build/ipython/IPython'), dst)


        info('Adding ImageMagick')
        dest = os.path.join(frameworks_dir, 'ImageMagick')
        if os.path.exists(dest):
            shutil.rmtree(dest)
        shutil.copytree(os.path.expanduser('~/ImageMagick'), dest, True)
        shutil.copyfile('/usr/local/lib/libpng12.0.dylib', os.path.join(dest, 'lib', 'libpng12.0.dylib'))


        info('Installing prescipt')
        sf = [os.path.basename(s) for s in all_names]
        launcher_path = os.path.join(resource_dir, '__boot__.py')
        f = open(launcher_path, 'r')
        src = f.read()
        f.close()
        src = src.replace('import Image', 'from PIL import Image')
        src = re.sub('(_run\s*\(.*?.py.*?\))', '%s'%(
'''
sys.frameworks_dir = os.path.join(os.path.dirname(os.environ['RESOURCEPATH']), 'Frameworks')
sys.resources_location = os.path.join(os.environ['RESOURCEPATH'], 'resources')
sys.extensions_location = os.path.join(sys.frameworks_dir, 'plugins')
''') + r'\n\1', src)
        f = open(launcher_path, 'w')
        print >>f, 'import sys, os'
        f.write(src)
        f.close()

        info('\nAdding main scripts to site-packages')
        f = zipfile.ZipFile(os.path.join(self.dist_dir, APPNAME+'.app', 'Contents', 'Resources', 'lib', 'python'+sys.version[:3], 'site-packages.zip'), 'a', zipfile.ZIP_DEFLATED)
        for script in scripts['gui']+scripts['console']:
            f.write(script, script.partition('/')[-1])
        f.close()

        info('\nCreating console.app')
        contents_dir = os.path.dirname(resource_dir)
        cc_dir = os.path.join(contents_dir, 'console.app', 'Contents')
        os.makedirs(cc_dir)
        for x in os.listdir(contents_dir):
            if x == 'console.app':
                continue
            if x == 'Info.plist':
                plist = plistlib.readPlist(os.path.join(contents_dir, x))
                plist['LSUIElement'] = '1'
                plistlib.writePlist(plist, os.path.join(cc_dir, x))
            else:
                os.symlink(os.path.join('../..', x),
                           os.path.join(cc_dir, x))

        info('\nBuilding disk image')
        BuildAPP.makedmg(os.path.join(self.dist_dir, APPNAME+'.app'), APPNAME+'-'+VERSION)

def main():
    sys.argv[1:2] = ['py2app']
    d = os.path.dirname
    icon = os.path.abspath('icons/library.icns')
    if not os.access(icon, os.R_OK):
        raise Exception('No icon at '+icon)
    setup(
        name = APPNAME,
        app = [scripts['gui'][0]],
        cmdclass = { 'py2app' : BuildAPP },
        options  = { 'py2app' :
                     {
                         'optimize' : 2,
                         'dist_dir' : 'build/py2app',
                         'argv_emulation' : True,
                         'iconfile' : icon,
                         'frameworks': ['libusb.dylib', 'libunrar.dylib'],
                         'includes' : ['sip', 'pkg_resources', 'PyQt4.QtXml',
                                       'PyQt4.QtSvg', 'PyQt4.QtWebKit', 'commands',
                                       'mechanize', 'ClientForm', 'usbobserver',
                                       'genshi', 'calibre.web.feeds.recipes.*',
                                       'calibre.gui2.convert.*',
                                       'PyQt4.QtNetwork',
                                       'keyword', 'codeop', 'pydoc', 'readline',
                                       'BeautifulSoup',
                                       'dateutil', 'email.iterators',
                                       'email.generator', 'sqlite3.dump',
                                       'calibre.ebooks.metadata.amazon',
                                       ],
                         'packages' : ['PIL', 'Authorization', 'lxml', 'dns'],
                         'excludes' : ['IPython'],
                         'plist'    : { 'CFBundleGetInfoString' : '''calibre, an E-book management application.'''
                                        ''' Visit http://calibre.kovidgoyal.net for details.''',
                                        'CFBundleIdentifier':'net.kovidgoyal.calibre',
                                        'CFBundleShortVersionString':VERSION,
                                        'CFBundleVersion':APPNAME + ' ' + VERSION,
                                        'LSMinimumSystemVersion':'10.4.3',
                                        'LSMultipleInstancesProhibited':'true',
                                        'NSHumanReadableCopyright':'Copyright 2008, Kovid Goyal',
                                        'LSEnvironment':{
                                                         'FC_CONFIG_DIR':'@executable_path/../Resources/fonts',
                                                         'MAGICK_HOME':'@executable_path/../Frameworks/ImageMagick',
                                                         'DYLD_LIBRARY_PATH':'@executable_path/../Frameworks/ImageMagick/lib',
                                                         }
                                       },
                      },
                    },
        setup_requires = ['py2app'],
        )
    return 0

