"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)

Utility functions for the Admin application
===========================================
"""
import os
import sys
import traceback
import zipfile
import urllib
from shutil import rmtree
from utils import web2py_uuid
from fileutils import w2p_pack, w2p_unpack, w2p_pack_plugin, w2p_unpack_plugin
from fileutils import up, fix_newlines, abspath, recursive_unlink
from fileutils import read_file, write_file, parse_version
from restricted import RestrictedError
from settings import global_settings

def apath(path='', r=None):
    """
    Builds a path inside an application folder

    Parameters
    ----------
    path:
        path within the application folder
    r:
        the global request object

    """

    opath = up(r.folder)
    while path[:3] == '../':
        (opath, path) = (up(opath), path[3:])
    return os.path.join(opath, path).replace('\\', '/')


def app_pack(app, request):
    """
    Builds a w2p package for the application

    Parameters
    ----------
    app:
        application name
    request:
        the global request object

    Returns
    -------
    filename:
        filename of the w2p file or None on error
    """
    try:
        app_cleanup(app, request)
        filename = apath('../deposit/%s.w2p' % app, request)
        w2p_pack(filename, apath(app, request))
        return filename
    except Exception:
        return False


def app_pack_compiled(app, request):
    """
    Builds a w2p bytecode-compiled package for the application

    Parameters
    ----------
    app:
        application name
    request:
        the global request object

    Returns
    -------
    filename:
        filename of the w2p file or None on error
    """

    try:
        filename = apath('../deposit/%s.w2p' % app, request)
        w2p_pack(filename, apath(app, request), compiled=True)
        return filename
    except Exception:
        return None

def app_cleanup(app, request):
    """
    Removes session, cache and error files

    Parameters
    ----------
    app:
        application name
    request:
        the global request object
    """
    r = True

    # Remove error files
    path = apath('%s/errors/' % app, request)
    if os.path.exists(path):
        for f in os.listdir(path):
            try:
                if f[:1]!='.': os.unlink(os.path.join(path,f))
            except IOError:
                r = False

    # Remove session files
    path = apath('%s/sessions/' % app, request)
    if os.path.exists(path):
        for f in os.listdir(path):
            try:
                if f[:1]!='.': recursive_unlink(os.path.join(path,f))
            except IOError:
                r = False

    # Remove cache files
    path = apath('%s/sessions/' % app, request)
    if os.path.exists(path):
        for f in os.listdir(path):
            try:
                if f[:1]!='.': os.unlink(os.path.join(path,f))
            except IOError:
                r = False
    return r


def app_compile(app, request):
    """
    Compiles the application

    Parameters
    ----------
    app:
        application name
    request:
        the global request object
    """
    from compileapp import compile_application, remove_compiled_application
    folder = apath(app, request)
    try:
        compile_application(folder)
        return None
    except (Exception, RestrictedError):
        tb = traceback.format_exc(sys.exc_info)
        remove_compiled_application(folder)
        return tb

def app_create(app, request,force=False,key=None):
    """
    Create a copy of welcome.w2p (scaffolding) app

    Parameters
    ----------
    app:
        application name
    request:
        the global request object

    """
    try:
        path = apath(app, request)
        os.mkdir(path)
    except:
        if not force:
            return False
    try:
        w2p_unpack('welcome.w2p', path)
        for subfolder in ['models','views','controllers', 'databases',
                          'modules','cron','errors','sessions',
                          'languages','static','private','uploads']:
            subpath =  os.path.join(path,subfolder)
            if not os.path.exists(subpath):
                os.mkdir(subpath)
        db = os.path.join(path, 'models', 'db.py')
        if os.path.exists(db):
            data = read_file(db)
            data = data.replace('<your secret key>',
                                'sha512:'+(key or web2py_uuid()))
            write_file(db, data)
        return True
    except:
        rmtree(path)
        return False


def app_install(app, fobj, request, filename, overwrite=None):
    """
    Installs an application:

    - Identifies file type by filename
    - Writes `fobj` contents to the `../deposit/` folder
    - Calls `w2p_unpack()` to do the job.

    Parameters
    ----------
    app:
        new application name
    fobj:
        file object containing the application to be installed
    request:
        the global request object
    filename:
        original filename of the `fobj`, required to determine extension

    Returns
    -------
    upname:
        name of the file where app is temporarily stored or `None` on failure
    """
    did_mkdir = False
    if filename[-4:] == '.w2p':
        extension = 'w2p'
    elif filename[-7:] == '.tar.gz':
        extension = 'tar.gz'
    else:
        extension = 'tar'
    upname = apath('../deposit/%s.%s' % (app, extension), request)

    try:
        write_file(upname, fobj.read(), 'wb')
        path = apath(app, request)
        if not overwrite:
            os.mkdir(path)
            did_mkdir = True
        w2p_unpack(upname, path)
        if extension != 'tar':
            os.unlink(upname)
        fix_newlines(path)
        return upname
    except Exception:
        if did_mkdir:
            rmtree(path)
        return False


def app_uninstall(app, request):
    """
    Uninstalls the application.

    Parameters
    ----------
    app:
        application name
    request:
        the global request object

    Returns
    -------
    `True` on success, `False` on failure
    """
    try:
        # Hey App, this is your end...
        path = apath(app, request)
        rmtree(path)
        return True
    except Exception:
        return False

def plugin_pack(app, plugin_name, request):
    """
    Builds a w2p package for the application

    Parameters
    ----------
    app:
        application name
    plugin_name:
        the name of the plugin without plugin_ prefix
    request:
        the current request app

    Returns
    -------
    filename:
        filename of the w2p file or None on error
    """
    try:
        filename = apath('../deposit/web2py.plugin.%s.w2p' % plugin_name, request)
        w2p_pack_plugin(filename, apath(app, request), plugin_name)
        return filename
    except Exception:
        return False

def plugin_install(app, fobj, request, filename):
    """
    Installs an application:

    - Identifies file type by filename
    - Writes `fobj` contents to the `../deposit/` folder
    - Calls `w2p_unpack()` to do the job.

    Parameters
    ----------
    app:
        new application name
    fobj:
        file object containing the application to be installed
    request:
        the global request object
    filename:
        original filename of the `fobj`, required to determine extension

    Returns
    -------
    upname:
        name of the file where app is temporarily stored or `None` on failure
    """

    upname = apath('../deposit/%s' % filename, request)

    try:
        write_file(upname, fobj.read(), 'wb')
        path = apath(app, request)
        w2p_unpack_plugin(upname, path)
        fix_newlines(path)
        return upname
    except Exception:
        os.unlink(upname)
        return False

def check_new_version(myversion, version_URL):
    """
    Compares current web2py's version with the latest stable web2py version.

    Parameters
    ----------
    myversion:
        the current version as stored in file `web2py/VERSION`
    version_URL:
        the URL that contains the version of the latest stable release

    Returns
    -------
    state:
        `True` if upgrade available, `False` if current version if up-to-date,
        -1 on error
    version:
        the most up-to-version available
    """
    try:
        from urllib import urlopen
        version = parse_version(urlopen(version_URL).read())
    except Exception:
        return -1, myversion

    if version > myversion:
        return True, version
    else:
        return False, version

def unzip(filename, dir, subfolder=''):
    """
    Unzips filename into dir (.zip only, no .gz etc)
    if subfolder!='' it unzip only files in subfolder
    """
    filename = abspath(filename)
    if not zipfile.is_zipfile(filename):
        raise RuntimeError, 'Not a valid zipfile'
    zf = zipfile.ZipFile(filename)
    if not subfolder.endswith('/'):
        subfolder = subfolder + '/'
    n = len(subfolder)
    for name in sorted(zf.namelist()):
        if not name.startswith(subfolder):
            continue
        #print name[n:]
        if name.endswith('/'):
            folder = os.path.join(dir,name[n:])
            if not os.path.exists(folder):
                os.mkdir(folder)
        else:
            write_file(os.path.join(dir, name[n:]), zf.read(name), 'wb')


def upgrade(request, url='http://web2py.com'):
    """
    Upgrades web2py (src, osx, win) is a new version is posted.
    It detects whether src, osx or win is running and downloads the right one

    Parameters
    ----------
    request:
        the current request object, required to determine version and path
    url:
        the incomplete url where to locate the latest web2py
        actual url is url+'/examples/static/web2py_(src|osx|win).zip'

    Returns
    -------
        True on success, False on failure (network problem or old version)
    """
    web2py_version = request.env.web2py_version
    gluon_parent = request.env.gluon_parent
    if not gluon_parent.endswith('/'):
        gluon_parent = gluon_parent + '/'
    (check, version) = check_new_version(web2py_version,
                                         url+'/examples/default/version')
    if not check:
        return (False, 'Already latest version')
    if os.path.exists(os.path.join(gluon_parent, 'web2py.exe')):
        version_type = 'win'
        destination = gluon_parent
        subfolder = 'web2py/'
    elif gluon_parent.endswith('/Contents/Resources/'):
        version_type = 'osx'
        destination = gluon_parent[:-len('/Contents/Resources/')]
        subfolder = 'web2py/web2py.app/'
    else:
        version_type = 'src'
        destination = gluon_parent
        subfolder = 'web2py/'

    full_url = url + '/examples/static/web2py_%s.zip' % version_type
    filename = abspath('web2py_%s_downloaded.zip' % version_type)
    file = None
    try:
        write_file(filename, urllib.urlopen(full_url).read(), 'wb')
    except Exception,e:
        return False, e
    try:
        unzip(filename, destination, subfolder)
        return True, None
    except Exception,e:
        return False, e

def add_path_first(path):
    sys.path = [path]+[p for p in sys.path if (not p==path and not p==(path+'/'))]

def create_missing_folders():
    if not global_settings.web2py_runtime_gae:
        for path in ('applications', 'deposit', 'site-packages', 'logs'):
            path = abspath(path, gluon=True)
            if not os.path.exists(path):
                os.mkdir(path)
    paths = (global_settings.gluon_parent, abspath('site-packages', gluon=True),  abspath('gluon', gluon=True), '')
    [add_path_first(path) for path in paths]

def create_missing_app_folders(request):
    if not global_settings.web2py_runtime_gae:
        if request.folder not in global_settings.app_folders:
            for subfolder in ('models', 'views', 'controllers', 'databases',
                              'modules', 'cron', 'errors', 'sessions',
                              'languages', 'static', 'private', 'uploads'):
                path =  os.path.join(request.folder, subfolder)
                if not os.path.exists(path):
                    os.mkdir(path)
            global_settings.app_folders.add(request.folder)



