#!/usr/bin/env python

import os
import json
import logging
import argparse
import socket
from threading import Timer
import subprocess

from bottle import Bottle, run, template, static_file, request, response, BaseRequest, default_app
from .indi_server import IndiServer, INDI_PORT, INDI_FIFO, INDI_CONFIG_DIR
from .driver import DeviceDriver, DriverCollection, INDI_DATA_DIR
from .database import Database
from .device import Device
from .indihub_agent import IndiHubAgent

# default settings
WEB_HOST = '0.0.0.0'
WEB_PORT = 8624

# Make it 10MB
BaseRequest.MEMFILE_MAX = 50 * 1024 * 1024

pkg_path, _ = os.path.split(os.path.abspath(__file__))
views_path = os.path.join(pkg_path, 'views')

parser = argparse.ArgumentParser(
    description='INDI Web Manager. '
    'A simple web application to manage an INDI server')

parser.add_argument('--indi-port', '-p', type=int, default=INDI_PORT,
                    help='indiserver port (default: %d)' % INDI_PORT)
parser.add_argument('--port', '-P', type=int, default=WEB_PORT,
                    help='Web server port (default: %d)' % WEB_PORT)
parser.add_argument('--host', '-H', default=WEB_HOST,
                    help='Bind web server to this interface (default: %s)' %
                    WEB_HOST)
parser.add_argument('--fifo', '-f', default=INDI_FIFO,
                    help='indiserver FIFO path (default: %s)' % INDI_FIFO)
parser.add_argument('--conf', '-c', default=INDI_CONFIG_DIR,
                    help='INDI config. directory (default: %s)' % INDI_CONFIG_DIR)
parser.add_argument('--xmldir', '-x', default=INDI_DATA_DIR,
                    help='INDI XML directory (default: %s)' % INDI_DATA_DIR)
parser.add_argument('--verbose', '-v', action='store_true',
                    help='Print more messages')
parser.add_argument('--logfile', '-l', help='log file name')
parser.add_argument('--server', '-s', default='standalone',
                    help='HTTP server [standalone|apache] (default: standalone')

args = parser.parse_args()


logging_level = logging.WARNING

if args.verbose:
    logging_level = logging.DEBUG

if args.logfile:
    logging.basicConfig(filename=args.logfile,
                        format='%(asctime)s - %(levelname)s: %(message)s',
                        level=logging_level)

else:
    logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s',
                        level=logging_level)

logging.debug("command line arguments: " + str(vars(args)))

hostname = socket.gethostname()

collection = DriverCollection(args.xmldir)
indi_server = IndiServer(args.fifo, args.conf)
indi_device = Device()

indihub_agent = IndiHubAgent('%s:%d' % (args.host, args.port), hostname, args.port)

db_path = os.path.join(args.conf, 'profiles.db')
db = Database(db_path)

collection.parse_custom_drivers(db.get_custom_drivers())

if args.server == 'standalone':
    app = Bottle()
    logging.info('using Bottle as standalone server')
else:
    app = default_app()
    logging.info('using Apache web server')

saved_profile = None
active_profile = ""


def start_profile(profile):
    info = db.get_profile(profile)

    profile_drivers = db.get_profile_drivers_labels(profile)
    all_drivers = [collection.by_label(d['label']) for d in profile_drivers]

    # Find if we have any remote drivers
    remote_drivers = db.get_profile_remote_drivers(profile)
    if remote_drivers:
        drivers = remote_drivers['drivers'].split(',')
        for drv in drivers:
            all_drivers.append(DeviceDriver(drv, drv, "1.0", drv, "Remote"))

    if all_drivers:
        indi_server.start(info['port'], all_drivers)
        # Auto connect drivers in 3 seconds if required.
        if info['autoconnect'] == 1:
            t = Timer(3, indi_server.auto_connect)
            t.start()


@app.route('/static/<path:path>')
def callback(path):
    """Serve static files"""
    return static_file(path, root=views_path)


@app.route('/favicon.ico', method='GET')
def get_favicon():
    """Serve favicon"""
    return static_file('favicon.ico', root=views_path)


@app.route('/')
def main_form():
    """Main page"""
    global saved_profile
    drivers = collection.get_families()

    if not saved_profile:
        saved_profile = request.get_cookie('indiserver_profile') or 'Simulators'

    profiles = db.get_profiles()
    return template(os.path.join(views_path, 'form.tpl'), profiles=profiles,
                    drivers=drivers, saved_profile=saved_profile,
                    hostname=hostname)

###############################################################################
# Profile endpoints
###############################################################################


@app.get('/api/profiles')
def get_json_profiles():
    """Get all profiles (JSON)"""
    results = db.get_profiles()
    return json.dumps(results)


@app.get('/api/profiles/<item>')
def get_json_profile(item):
    """Get one profile info"""
    results = db.get_profile(item)
    return json.dumps(results)


@app.post('/api/profiles/<name>')
def add_profile(name):
    """Add new profile"""
    db.add_profile(name)


@app.delete('/api/profiles/<name>')
def delete_profile(name):
    """Delete Profile"""
    db.delete_profile(name)


@app.put('/api/profiles/<name>')
def update_profile(name):
    """Update profile info (port & autostart & autoconnect)"""
    response.set_cookie("indiserver_profile", name,
                        None, max_age=3600000, path='/')
    data = request.json
    port = data.get('port', args.indi_port)
    autostart = bool(data.get('autostart', 0))
    autoconnect = bool(data.get('autoconnect', 0))
    db.update_profile(name, port, autostart, autoconnect)


@app.post('/api/profiles/<name>/drivers')
def save_profile_drivers(name):
    """Add drivers to existing profile"""
    data = request.json
    db.save_profile_drivers(name, data)


@app.post('/api/profiles/custom')
def save_profile_custom_driver():
    """Add custom driver to existing profile"""
    data = request.json
    db.save_profile_custom_driver(data)
    collection.clear_custom_drivers()
    collection.parse_custom_drivers(db.get_custom_drivers())


@app.get('/api/profiles/<item>/labels')
def get_json_profile_labels(item):
    """Get driver labels of specific profile"""
    results = db.get_profile_drivers_labels(item)
    return json.dumps(results)


@app.get('/api/profiles/<item>/remote')
def get_remote_drivers(item):
    """Get remote drivers of specific profile"""
    results = db.get_profile_remote_drivers(item)
    if results is None:
        results = {}
    return json.dumps(results)


###############################################################################
# Server endpoints
###############################################################################

@app.get('/api/server/status')
def get_server_status():
    """Server status"""
    status = [{'status': str(indi_server.is_running()), 'active_profile': active_profile}]
    return json.dumps(status)


@app.get('/api/server/drivers')
def get_server_drivers():
    """List server drivers"""
    # status = []
    # for driver in indi_server.get_running_drivers():
    #     status.append({'driver': driver})
    # return json.dumps(status)
    # labels = []
    # for label in sorted(indi_server.get_running_drivers().keys()):
    #     labels.append({'driver': label})
    # return json.dumps(labels)
    drivers = []
    if indi_server.is_running() is True:
        for driver in indi_server.get_running_drivers().values():
            drivers.append(driver.__dict__)
    return json.dumps(drivers)


@app.post('/api/server/start/<profile>')
def start_server(profile):
    """Start INDI server for a specific profile"""
    global saved_profile
    saved_profile = profile
    global active_profile
    active_profile = profile
    response.set_cookie("indiserver_profile", profile,
                        None, max_age=3600000, path='/')
    start_profile(profile)


@app.post('/api/server/stop')
def stop_server():
    """Stop INDI Server"""
    indihub_agent.stop()
    indi_server.stop()

    global active_profile
    active_profile = ""

    # If there is saved_profile already let's try to reset it
    global saved_profile
    if saved_profile:
        saved_profile = request.get_cookie("indiserver_profile") or "Simulators"


###############################################################################
# Driver endpoints
###############################################################################

@app.get('/api/drivers/groups')
def get_json_groups():
    """Get all driver families (JSON)"""
    response.content_type = 'application/json'
    families = collection.get_families()
    return json.dumps(sorted(families.keys()))


@app.get('/api/drivers')
def get_json_drivers():
    """Get all drivers (JSON)"""
    response.content_type = 'application/json'
    return json.dumps([ob.__dict__ for ob in collection.drivers])


@app.post('/api/drivers/start/<label>')
def start_driver(label):
    """Start INDI driver"""
    driver = collection.by_label(label)
    indi_server.start_driver(driver)
    logging.info('Driver "%s" started.' % label)


@app.post('/api/drivers/stop/<label>')
def stop_driver(label):
    """Stop INDI driver"""
    driver = collection.by_label(label)
    indi_server.stop_driver(driver)
    logging.info('Driver "%s" stopped.' % label)


@app.post('/api/drivers/restart/<label>')
def restart_driver(label):
    """Restart INDI driver"""
    driver = collection.by_label(label)
    indi_server.stop_driver(driver)
    indi_server.start_driver(driver)
    logging.info('Driver "%s" restarted.' % label)

###############################################################################
# Device endpoints
###############################################################################


@app.get('/api/devices')
def get_devices():
    return json.dumps(indi_device.get_devices())

###############################################################################
# System control endpoints
###############################################################################


@app.post('/api/system/reboot')
def system_reboot():
    """reboot the system running indi-web"""
    logging.info('System reboot, stopping server...')
    stop_server()
    logging.info('rebooting...')
    subprocess.call('reboot')


@app.post('/api/system/poweroff')
def system_poweroff():
    """poweroff the system"""
    logging.info('System poweroff, stopping server...')
    stop_server()
    logging.info('poweroff...')
    subprocess.run("poweroff")

###############################################################################
# INDIHUB Agent control endpoints
###############################################################################


@app.get('/api/indihub/status')
def get_indihub_status():
    """INDIHUB Agent status"""
    mode = indihub_agent.get_mode()
    is_running = indihub_agent.is_running()
    response.content_type = 'application/json'
    status = [{'status': str(is_running), 'mode': mode, 'active_profile': active_profile}]
    return json.dumps(status)


@app.post('/api/indihub/mode/<mode>')
def change_indihub_agent_mode(mode):
    """Change INDIHUB Agent mode with a current INDI-profile"""

    if active_profile == "" or not indi_server.is_running():
        response.content_type = 'application/json'
        response.status = 500
        return json.dumps({'message': 'INDI-server is not running. You need to run INDI-server first.'})

    indihub_agent.stop()

    if mode == 'off':
        return

    indihub_agent.start(active_profile, mode)


###############################################################################
# Startup standalone server
###############################################################################


def main():
    """Start autostart profile if any"""
    global active_profile

    for profile in db.get_profiles():
        if profile['autostart']:
            start_profile(profile['name'])
            active_profile = profile['name']
            break

    run(app, host=args.host, port=args.port, quiet=args.verbose)
    logging.info("Exiting")


# JM 2018-12-24: Added __main__ so I can debug this as a module in PyCharm
# Otherwise, I couldn't get it to run main as all
if __name__ == '__init__' or __name__ == '__main__':
    main()