#!/usr/bin/env python
from __future__ import print_function, division, unicode_literals
from tornado.escape import json_encode, json_decode
import tornado.ioloop
from tornado.web import Application, url
import yaml

from hcam_drivers.utils.web import (BaseHandler, HServerException,
                                    MSG_TEMPLATE)

# not needed on deployment version
import time
import os
import sqlite3
# This imitates the "thin client" that runs on the rack PC.
# The thin client acts as a bridge between client software on
# the DRPC and the ESO software, controlling the camera and
# receiving info about the current status


KNOWN_PARAMS = {
    'DET.NDIT': 10,
    'DET.SEQ1.DIT': 5,
    'DET.SEQ2.DIT': 5,
    'DET.SEQ3.DIT': 5,
    'DET.SEQ4.DIT': 5,
    'DET.SEQ5.DIT': 5,
}


DATABASE = 'test_server.db'
PAST_TIME = 1400000000


def create_db():
    if os.path.exists(DATABASE):
        os.unlink(DATABASE)
    with sqlite3.connect(DATABASE) as conn:
        conn.execute(
            'CREATE TABLE run (id INT, startTime FLOAT' +
            ', NDIT INT, DIT FLOAT, stateName TEXT, run INT, expState TEXT)'
        )
        keys = ('DET.SEQ{}.DIT'.format(seq+1) for seq in range(5))
        DIT = max([KNOWN_PARAMS[key] for key in keys])
        conn.execute(
            "INSERT INTO run (id, startTime, NDIT, DIT, stateName, run, expState) " +
            "VALUES (?, ?, ?, ?, ?, ?, ?)",
            (1, PAST_TIME, KNOWN_PARAMS['DET.NDIT'], DIT, 'LOADED', 1, "idle")
        )
        conn.commit()


def get_db():
    db = sqlite3.connect(DATABASE)
    db.row_factory = sqlite3.Row
    return db


def close_db(db):
    db.close()


def query_db(query, args=(), one=False):
    conn = get_db()
    cur = conn.execute(query, args)
    rv = cur.fetchall()
    conn.commit()
    cur.close()
    close_db(conn)
    return (rv[0] if rv else None) if one else rv


def parse_response(response):
    return json_encode(yaml.load(response))


class StartHandler(BaseHandler):
    def get(self):
        """
        Start a run
        """
        query_db("update run set startTime = ? WHERE id = 1", [time.time()])
        query_db("update run set expState = ? WHERE id = 1", ["active"])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class StopHandler(BaseHandler):
    """
    Stop a run, returning intermediate product
    """
    def get(self):
        # fake stopping run by setting starttime a long time in the past
        query_db("update run set startTime = ? WHERE id = 1", [PAST_TIME])
        # add 1 to run number
        row = query_db("select * from run", one=True)
        run_number = int(row['run'])
        run_number += 1
        query_db("update run set run = ? WHERE id = 1", [run_number])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class AbortHandler(BaseHandler):
    """
    Abort a run
    """
    def get(self):
        # fake stopping run by setting starttime a long time in the past
        query_db("update run set startTime = ? WHERE id = 1", [PAST_TIME])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class OnlineHandler(BaseHandler):
    """
    Bring server online, powering on NGC controller
    """
    def get(self):
        query_db("update run set stateName = ? WHERE id = 1", ['ONLINE'])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class OffHandler(BaseHandler):
    """
    Bring server to OFF state. All sub-processes terminate
    """
    def get(self):
        query_db("update run set stateName = ? WHERE id = 1", ['OFF'])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class StandbyHandler(BaseHandler):
    """
    Bring server to standby state.

    All sub-processes are disabled, but server can communicate.
    """
    def get(self):
        query_db("update run set stateName = ? WHERE id = 1", ['STANDBY'])
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class ResetHandler(BaseHandler):
    """
    Resets the NGC controller front end
    """
    def get(self):
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))


class SummaryHandler(BaseHandler):
    """
    Get status summary of server and exposure from the database
    """
    def get(self):
        row = query_db("select * from run", one=True)
        elapsedTime = time.time() - row['startTime']
        countDown = row['DIT']*row['NDIT'] - elapsedTime
        run = int(row['run'])
        if elapsedTime > row['DIT']*row['NDIT']:
            expStatusName = "success"
            subState = "idle"
            if row['expState'] != 'idle':
                query_db("update run set run = ? WHERE id = 1", [run+1])
                query_db("update run set expState = ? WHERE id = 1", ["idle"])
        else:
            expStatusName = "integrating"
            subState = "active"

        summary_dictionary = {
            "RETCODE": "OK",
            "exposure.baseName": "/home/observer/ngc",
            "exposure.countDown": str(countDown),
            "exposure.expStatusName": expStatusName,
            "exposure.newDataFileName": "ngc{:05d}.fits".format(run),
            "exposure.time": "6",
            "system.stateName": row['stateName'],
            "system.subStateName": subState
        }
        self.set_header('Content-Type', 'application/json')
        self.write(json_encode(summary_dictionary))


class StatusHandler(BaseHandler):
    """
    Check status, either of the server as a whole, or get/set
    parameter of the current sequencer file.
    """
    def get(self, param_id=None):
        self.set_header('Content-Type', 'application/json')
        if param_id:
            # get status of requested parameter
            if param_id not in KNOWN_PARAMS:
                raise HServerException(reason='Invalid Parameter',
                                       status_code=404)
            ret_value = "{} {}".format(param_id, KNOWN_PARAMS[param_id])
            response = MSG_TEMPLATE.format(ret_value, "OK")
            self.set_header('Content-Type', 'application/json')
            self.finish(parse_response(response))
        else:
            # get server state
            row = query_db("select * from run", one=True)
            status = row['stateName']
            self.set_header('Content-Type', 'application/json')
            self.finish(parse_response(MSG_TEMPLATE.format(status, "OK")))

    def post(self, param_id):
        if param_id not in KNOWN_PARAMS:
            raise HServerException(reason='Invalid Parameter',
                                   status_code=404)
        try:
            req_json = json_decode(self.request.body.decode())
            if not req_json or 'value' not in req_json:
                raise HServerException(reason='No value supplied',
                                       status_code=400)

            KNOWN_PARAMS[param_id] = req_json['value']
            if param_id.startswith('DET.SEQ'):
                query_db("update run set DIT = ? WHERE id = 1", [req_json['value']])
            else:
                query_db("update run set NDIT = ? WHERE id = 1", [req_json['value']])

            self.set_header('Content-Type', 'application/json')
            self.write(parse_response(MSG_TEMPLATE.format("OK", "OK")))
        except:
            raise HServerException(reason='Error posting status', status_code=500)


class PostRunHandler(BaseHandler):
    """
    Post an XML file to the server, updating multiple settings.

    Client should parse XML file and make HTML post request with data json encoded
    """
    def post(self):
        req_json = json_decode(self.request.body.decode())
        if not req_json or 'appdata' not in req_json:
                raise HServerException(reason='No appdata supplied',
                                       status_code=400)
        response = MSG_TEMPLATE.format("Not currently supported", "OK")
        self.set_header('Content-Type', 'application/json')
        self.write(parse_response(response))


if __name__ == '__main__':
    db = {}
    app = Application([
        url(r'/start', StartHandler, dict(db=db), name="start"),
        url(r'/stop', StopHandler, dict(db=db), name="stop"),
        url(r'/abort', AbortHandler, dict(db=db), name="abort"),
        url(r'/online', OnlineHandler, dict(db=db), name="online"),
        url(r'/off', OffHandler, dict(db=db), name="off"),
        url(r'/reset', ResetHandler, dict(db=db), name="reset"),
        url(r'/summary', SummaryHandler, dict(db=db), name="summary"),
        url(r'/status', StatusHandler, dict(db=db)),
        url(r'/setup', PostRunHandler, dict(db=db), name='setup'),
        url(r"/status/(.*)", StatusHandler, dict(db=db))
    ], debug=True)
    app.listen(5000)
    tornado.ioloop.IOLoop.current().start()
