from __future__ import print_function
import sys
import os
import json
import logging
import webbrowser
import string
import socket

import subprocess
from logging import Formatter
from tempfile import gettempdir

from launcher import __version__

from PyQt4.QtCore import QUrl
from PyQt4.QtGui import QApplication
from PyQt4.QtWebKit import QWebView, QWebSettings

from flask import Flask, send_from_directory
from flask import Response
from flask import request

app = Flask(__name__)

server_host = 'localhost'
server_port = 4000

app.debug = False

tempdir = gettempdir()
logfile = os.path.join(tempdir, 'launcher.log')
print("logs are at:", logfile)
file_handler = logging.FileHandler(logfile)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(Formatter(
    '%(asctime)s %(levelname)s: %(message)s '
    '[in %(pathname)s:%(lineno)d]'
))


app.logger.addHandler(file_handler)
app.logger.setLevel(logging.DEBUG)

# We need to use this so that it can be pickled by Process
def run(*args, **kwargs):
    return app.run(*args, **kwargs)

import conda
from conda import api

pythonVersionsDict = {}

ui_base_dir = 'static/'

# Dict of app process objects that have been launched.
app_processes = {}

#----------------------------------------------------------------
# FUNCTIONS
#----------------------------------------------------------------
def changeDelimiter(packagesList):
    retVal = []
    for pID in packagesList:
        x = pID.rsplit('-',2)
        retVal.extend([ x[0]+'|'+x[1]+'|'+x[2] ])
    return retVal


def removeDuplicates(inList):
    outList = []
    for x in inList:
        if x not in outList:
            outList.append(x)
    return outList

def apiErrorResponse():
    js = json.dumps({ 'error': 'conda api module not found.'})
    return Response(js, status=200, mimetype='application/json')

def missingRequestParameterResponse(param):
    js = json.dumps({ 'error': 'The request is missing a parameter [%s]' % param})
    return Response(js, status=200, mimetype='application/json')

def failedToLaunchResponse(app):
    js = json.dumps({ 'error': 'Failed to launch app [%s]' % app })
    return Response(js, status=200, mimetype='application/json')

def isAppInstalled(app):
    retVal = None
    if api is not None:
        retVal = api.app_is_installed(app)
    return retVal

def isAppFeatured(app):
    return False

#----------------------------------------------------------------
# ROUTES
#----------------------------------------------------------------
@app.route('/')
def launcher():
    return send_from_directory(os.path.join(app.root_path, ui_base_dir),
                            'index.html', mimetype="text/html")


@app.route('/api/', methods = ['GET'])
def api_index():
    return send_from_directory(os.path.join(app.root_path, 'static/api'),
                            'api_index.html', mimetype="text/html")


@app.route('/api/launcher_info/', methods = ['GET'])
def launcher_info():
    js = json.dumps({ 'version': __version__, 'logfile':logfile, 'port':str(server_port) })
    resp = Response(js, status=200, mimetype='application/json')
    return resp


@app.route('/api/app/index', methods = ['GET'])
def app_index():
    app.logger.info('/api/app/index')
    if api is not None:

        # Get the full app index
        app_index = api.app_get_index()

        # Setup the filters
        kw_filters = False
        kw_installed_text = 'installed'
        kw_feature_text = 'featured'
        kw_installed = False
        kw_featured = False
        kw_app_val = None
        kw_filter_val = None
        kw_pretty_print = request.args.get('prettyPrint', False)

        param = request.args.get('keywords',None)
        if param is not None:
            app.logger.info('keywords param found.')
            val_lower = param.lower().strip()
            if len(val_lower) > 0:
                app.logger.info(val_lower)
                kw_installed = kw_installed_text in val_lower
                kw_featured = kw_feature_text in val_lower
                kw_filters = kw_featured or kw_installed

        param = request.args.get('app', None)
        if param is not None:
            app.logger.info('app param found.')
            val_lower = param.lower().strip()
            if len(val_lower) > 0:
                app.logger.info(val_lower)
                kw_app_val = param
                kw_filters = True

        param = request.args.get('filter', None)
        if param is not None:
            app.logger.info('filter param found.')
            val_lower = param.lower().strip()
            if len(val_lower) > 0:
                app.logger.info(val_lower)
                kw_filter_val = param
                kw_filters = True

        # Loop through the app index and build up the response data.
        resp_data = {}
        for k in app_index:
            include_app = True
            app_obj = app_index[k]
            try:
                # remove the icon entry because it contains binary data that is not json compatible.
                # Ilan will change this to return a url for the icon.
                #del app['icon']
                pass
            except KeyError:
                pass

            app.logger.info("app: %s" % k)

            #for k2 in app_obj:
                 #app.logger.info('     %s: %s' % (k2, app_obj[k2],))

            # If a filter parameter was specified then loop over the apps and include
            # the apps that aren't filtered out.
            if kw_filters:
                app_name = app_obj['name']
                app_summary = app_obj['summary']
                if kw_app_val is not None:
                    include_app = string.find(app_name, kw_app_val) > -1
                    if not include_app:
                        continue

                if kw_filter_val is not None:
                    # check the app name
                    include_app = string.find(app_name.lower(), kw_filter_val) > -1
                    if not include_app:
                        # check the app summary
                        include_app = string.find(app_summary.lower(), kw_filter_val) > -1
                        if not include_app:
                            continue

                if kw_installed:
                    if isAppInstalled(app_name) is None:
                        continue

                if kw_featured:
                    include_app = isAppFeatured(app_name)
                    if not include_app:
                        continue

            if include_app:
                # Add installed flag to app meta data.
                # This is not in the index by default since the app server
                # does not know which apps are installed.
                app_obj['installed'] = isAppInstalled(k)
                app_obj['icon_url'] = api.app_get_icon_url(k)

                # add app to response data
                resp_data[k] = app_obj

        resp_data = { 'apps' : resp_data }

        if kw_pretty_print:
            js = json.dumps(resp_data, sort_keys=True, indent=4, separators=(',',': '))
        else:
            js = json.dumps(resp_data)

        resp = Response(js, status=200, mimetype='application/json')
    else:
        resp = apiErrorResponse()

    return resp

@app.route('/api/app/install', methods = ['GET'])
def app_install():
    app_id = request.args.get('app_id', None)
    prefix = request.args.get('prefix', conda.config.root_dir)

    if api is not None:
        if app_id is not None:
            try:
                api.app_install(app_id, prefix)
                js = json.dumps({ 'app_id': app_id, 'prefix':prefix, 'error':'' })
            except Exception as e:
                app.logger.error(e)
                js = json.dumps({ 'error': 'Failed to install app [%s].  Error: [%r]' % (app_id, e,) })
            resp = Response(js, status=200, mimetype='application/json')
        else:
            resp = missingRequestParameterResponse('app_id')
    else:
        resp = apiErrorResponse()
    return resp

@app.route('/api/app/uninstall', methods=['GET'])
def app_uninstall():
    app_id = request.args.get('app_id', None)
    prefix = request.args.get('prefix', conda.config.root_dir)

    if api is not None:
        if app_id is not None:
            try:
                api.app_uninstall(app_id, prefix)
                js = json.dumps({'app_id': app_id, 'prefix': prefix, 'error':
                    ''})
            except Exception as e:
                app.logger.error(e)
                js = json.dumps({'error': ('Failed to uninstall app [%s]. '
                    'Error: [%r]') % (app_id, e,)})
            resp = Response(js, status=200, mimetype='application/json')
        else:
            resp = missingRequestParameterResponse('app_id')
    else:
        resp = apiErrorResponse()
    return resp

@app.route('/api/app/launch', methods = ['GET'])
def app_launch():
    if api is not None:
        app_id = request.args.get('app_id', None)
        prefix = request.args.get('prefix', conda.config.root_dir)
        app.logger.info('app_id=%s' % app_id)
        app.logger.info('prefix=%s' % prefix)
        if app_id is not None:
           proc_obj = api.app_launch(app_id, prefix=prefix)
           if proc_obj is not None:
               app_processes[app_id] = proc_obj
               app.logger.info(proc_obj)
               pid = proc_obj.pid
               app.logger.info('pid=%s' % pid)
               js = json.dumps({ 'app_id': app_id, 'prefix':prefix, 'pid': pid })
               resp = Response(js, status=200, mimetype='application/json')
           else:
               resp = failedToLaunchResponse(app_id)
        else:
            resp = missingRequestParameterResponse('app_id')
    else:
        resp = apiErrorResponse()
    return resp

@app.route('/api/openlink', methods = ['GET'])
def openlink():
    if api is not None:
        url = request.args.get('url', None)
        app.logger.info('url=%s' % url)
        if url is not None:
           webbrowser.open_new(url)
           js = json.dumps({})
           resp = Response(js, status=200, mimetype='application/json')
        else:
            resp = missingRequestParameterResponse('url')
    else:
        resp = apiErrorResponse()
    return resp

@app.route('/version', methods = ['GET'])
def getVersion():
    d = dict(req='version')
    ver = 'CE'
    if sys.version.find('AnacondaCE') < 0:
        ver = 'Pro'
    d.update(dict(version=ver, errorcode=0, errormsg=''))
    return Response(json.dumps(d), status=200, mimetype='application/json')


@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                            'favicon.ico', mimetype='image/x-icon')

def get_port(host, port, app):
    if port:
        return port
    port = 5000

    # Logic borrowed from Tornado's bind_sockets

    flags = socket.AI_PASSIVE
    if hasattr(socket, "AI_ADDRCONFIG"):
        flags |= socket.AI_ADDRCONFIG

    while True:
        try:
            res = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, 0,
                flags)
            af, socktype, proto, cannonname, sockaddr = res[0]

            s = socket.socket(af, socktype, proto)

            s.setblocking(0)
            s.bind(sockaddr)
            s.listen(128)
        except socket.error as e:
            app.logger.warning("Could not assign to port %s (%s).", port, e)
            port += 1
            continue
        return port

def main():
    from optparse import OptionParser

    p = OptionParser(usage="usage: %prog [options]")
    p.add_option(
        "--open-browser",
        default=False,
        action="store_true",
        help="Open the web browser (default=False)",
        )
    p.add_option(
        "--port",
        default=None,
        action="store",
        help="The port to use (default=automatic)",
        type=int,
        )
    p.add_option(
        "--no-qt",
        default=True,
        action="store_false",
        help="Open the QT app",
        dest='qt',
        )
    p.add_option(
        "--debug",
        default=False,
        action="store_true",
        help="Enable debugging",
        )
    p.add_option(
        "--version",
        action="store_true",
        )
    opts, args = p.parse_args()

    if opts.version:
        print('launcher: %s' % __version__)
        return

    if opts.qt and opts.open_browser:
        p.error('Only one of --qt and --open-browser allowed')

    if opts.debug:
        app.debug = True

    # Setup configuration.
    app.logger.info('====== STARTUP ======')
    global server_port
    server_port = get_port(server_host, opts.port, app)
    app.logger.info("port: %d", server_port)
    if opts.open_browser:
        launcherUrl = 'http://' + server_host + ':' + \
            str(server_port) + '/'
        app.logger.info('Opening web browser [%s]...' % launcherUrl)
        webbrowser.open_new(launcherUrl)
        app.run(host='', port=server_port)
    elif opts.qt:
        try:
            p = subprocess.Popen([sys.executable, __file__] +
                sys.argv[1:] + ['--no-qt'], env=os.environ)

            # Don't start until the server is running
            try:
                import requests
            except ImportError:
                pass
            else:
                app.logger.info("Waiting for the server to start...")
                while True:
                    try:
                        requests.get('http://%s:%d/' % (server_host,
                        server_port))
                    except requests.exceptions.ConnectionError:
                        pass
                    else:
                        break

            qtapp = QApplication(sys.argv)
            web = QWebView()
            if opts.debug:
                # web.settings().setAttribute(QWebSettings.WebAttribute.DeveloperExtrasEnabled,
                #     True)
                logging.basicConfig(level=logging.DEBUG)
                app.logger.debug('host ' + server_host)
                app.logger.debug('port ' + str(server_port))
            web.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows,
                True)
            web.settings().setAttribute(QWebSettings.LocalContentCanAccessRemoteUrls,
                True)
            web.load(QUrl("http://%s:%s/" % (server_host, server_port)))
            web.resize(960, 640)
            web.show()
            web.raise_()

            # Start QT app and block
            qtapp.exec_()
        finally:
            p.terminate()
    else:
        app.run(server_host, port=server_port)


    #http_server = WSGIServer((host, lConfig.settings['PORT']), app)
    #http_server.serve_forever()

if __name__ == '__main__':
    main()
