#!/usr/bin/env python
#
# Cloudlet Infrastructure for Mobile Computing
#
#   Author: Kiryong Ha <krha@cmu.edu>
#           Zhuo Chen <zhuoc@cs.cmu.edu>
#
#   Copyright (C) 2011-2013 Carnegie Mellon University
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#



import json
from optparse import OptionParser
import os
import Queue
import re
import sys
import threading
import time

dir_file = os.path.dirname(os.path.realpath(__file__))

sys.path.insert(0, os.path.join(dir_file, ".."))
import gabriel
import gabriel.control
LOG = gabriel.logging.getLogger(__name__)

REST_server = gabriel.network.RESTServer()
UPnP_server = gabriel.network.UPnPServer()
service_monitor = gabriel.control.OffloadingEngineMonitor(gabriel.control.image_queue_list,
        gabriel.control.acc_queue_list, gabriel.control.audio_queue_list, gabriel.control.result_queue)


def process_command_line(argv):
    VERSION = 'gabriel control server : %s' % gabriel.Const.VERSION
    DESCRIPTION = "Gabriel cognitive assistance"

    parser = OptionParser(usage = '%prog [option]', version = VERSION,
            description = DESCRIPTION)

    parser.add_option(
            '-e', '--emulation', action = 'store', dest = 'image_dir',
            help = "emulate mobile device using series of jpeg images")
    parser.add_option(
            '-k', '--skip_frames', type = "int", action = 'store', default = 0,
            help = "skip the first several frames in emulation mode")
    parser.add_option(
            '-r', '--frame_rate', type = "int", action = 'store', dest = 'frame_rate', default = 15,
            help = "the frame rate for loading jpeg images")
    parser.add_option(
            '--log_images', action = 'store_true',
            help = "set if want to store all received images")
    parser.add_option(
            '--log_images_path', action = 'store', default = None,
            help = "path (dir) to store all received images")
    parser.add_option(
            '--log_video', action = 'store_true',
            help = "set if want to store all received images into a video file")
    parser.add_option(
            '--log_video_path', action = 'store', default = None,
            help = "path to store log video file")
    parser.add_option(
            '-l', '--legacy_mode', action = 'store_true',
            help = "whether to use the legacy protocal. If true, the result is a pure JSON.")
    parser.add_option(
            '-n', '--net_interface', action = 'store', default = "eth0",
            help = "the network interface with which the cognitive engines communicate")
    settings, args = parser.parse_args(argv)

    if hasattr(settings, 'image_dir') and settings.image_dir is not None:
        if os.path.isdir(settings.image_dir) is False:
            parser.error("%s is not a directory" % settings.image_dir)
    return settings, args


class EmulatedMobileDevice(object):
    '''
    Emulation of a mobile device that generates images.
    Put the images into queues to be published to all cognitive engines in the same way as if they are coming from a real device.
    '''
    def __init__(self, image_dir, frame_rate, skip_frames):
        from os import listdir
        self.stop = threading.Event()
        self.filelist = [os.path.join(image_dir, f) for f in listdir(image_dir)
                if f.lower().endswith("jpeg") or f.lower().endswith("jpg") or f.lower().endswith("bmp")]
        self.filelist.sort()
        self.filelist = self.filelist[skip_frames:]
        self.wait_time = 1.0 / frame_rate

        if gabriel.Debug.SAVE_IMAGES:
            if not os.path.exists(gabriel.Const.LOG_IMAGES_PATH):
                os.makedirs(gabriel.Const.LOG_IMAGES_PATH)
            self.log_images_counter = 0
        if gabriel.Debug.SAVE_VIDEO:
            self.log_video_writer_created = False

    def serve_forever(self):
        frame_count = 0;
        while(not self.stop.wait(0.01)):
            for image_file in self.filelist:
                image_data = open(image_file, "r").read()
                for image_queue in gabriel.control.image_queue_list:
                    header_data = json.dumps({"type" : "emulated", "id" : frame_count})
                    if image_queue.full() is True:
                        image_queue.get()
                    image_queue.put((header_data, image_data))

                ## write images into files
                if gabriel.Debug.SAVE_IMAGES:
                    self.log_images_counter += 1
                    with open(os.path.join(gabriel.Const.LOG_IMAGES_PATH, "frame-" + gabriel.util.add_preceding_zeros(self.log_images_counter) + ".jpeg"), "w") as f:
                        f.write(image_data)

                ## write images into a video
                if gabriel.Debug.SAVE_VIDEO:
                    import cv2
                    import numpy as np
                    img_array = np.asarray(bytearray(image_data), dtype = np.int8)
                    cv_image = cv2.imdecode(img_array, -1)
                    if not self.log_video_writer_created:
                        self.log_video_writer_created = True
                        self.log_video_writer = cv2.VideoWriter(gabriel.Const.LOG_VIDEO_PATH, cv2.cv.CV_FOURCC('X','V','I','D'), 10, (cv_image.shape[1], cv_image.shape[0]))
                    self.log_video_writer.write(cv_image)

                if frame_count % 100 == 0:
                    pass
                    #LOG.info("pushing emualted image to the queue (%d)" % frame_count)
                frame_count += 1
                time.sleep(self.wait_time)

    def terminate(self):
        self.stop.set()
        pass


## TODO
def start_background_services():
    global REST_server
    global UPnP_server
    global service_monitor
    # start REST server for meta info
    try:
        REST_server.start()
        LOG.info("Start RESTful API Server (port :%s)" % \
                gabriel.Const.SERVICE_DISCOVERY_HTTP_PORT)
    except gabriel.RESTServerError as e:
        LOG.warning(str(e))
        LOG.warning("Cannot start REST API Server")
        REST_server = None

    # Start UPnP Server
    try:
        UPnP_server.start()
        LOG.info("Start UPnP Server")
    except gabriel.UPnPError as e:
        LOG.warning(str(e))
        LOG.warning("Cannot start UPnP Server")
        UPnP_server = None

    # Start Offloading Engine monitor
    try:
        service_monitor.start()
        LOG.info("Start monitoring offload engines")
    except UPnPError as e:
        LOG.warning(str(e))
        LOG.warning("Cannot start Offloading Engine Monitor")
        service_monitor = None


## TODO
def finish_background_services():
    global REST_server
    global UPnP_server
    global service_monitor

    if UPnP_server is not None:
        LOG.info("[TERMINATE] Terminate UPnP Server")
        UPnP_server.terminate()
        UPnP_server.join()
    if REST_server is not None:
        LOG.info("[TERMINATE] Terminate REST API monitor")
        REST_server.terminate()
        REST_server.join()
    if service_monitor is not None:
        LOG.info("[TERMINATE] Terminate Monitoring service")
        service_monitor.terminate()
        service_monitor.join()


def main():
    settings, args = process_command_line(sys.argv[1:])
    if settings.log_images:
        gabriel.Debug.SAVE_IMAGES = True
        if settings.log_images_path is not None:
            gabriel.Const.LOG_IMAGES_PATH = settings.log_images_path
    if settings.log_video:
        gabriel.Debug.SAVE_VIDEO = True
        if settings.log_images_path is not None:
            gabriel.Const.LOG_VIDEO_PATH = settings.log_video_path
    if settings.legacy_mode:
        gabriel.Const.LEGACY_JSON_ONLY_RESULT = True
    REST_server.set_interface(settings.net_interface)

    start_background_services()

    ## mobile servers that communicate with the mobile device (start with m_)
    m_control_server = gabriel.control.MobileCommServer(gabriel.Const.MOBILE_SERVER_CONTROL_PORT, gabriel.control.MobileControlHandler)
    if settings.image_dir:
        m_video_server = EmulatedMobileDevice(os.path.abspath(settings.image_dir), settings.frame_rate, settings.skip_frames)
    else:
        m_video_server = gabriel.control.MobileCommServer(gabriel.Const.MOBILE_SERVER_VIDEO_PORT, gabriel.control.MobileVideoHandler)
    m_acc_server = gabriel.control.MobileCommServer(gabriel.Const.MOBILE_SERVER_ACC_PORT, gabriel.control.MobileAccHandler)
    m_audio_server = gabriel.control.MobileCommServer(gabriel.Const.MOBILE_SERVER_AUDIO_PORT, gabriel.control.MobileAudioHandler)
    m_result_server = gabriel.control.MobileCommServer(gabriel.Const.MOBILE_SERVER_RESULT_PORT, gabriel.control.MobileResultHandler)
    ucomm_relay_server = gabriel.control.UCommRelayServer(gabriel.Const.UCOMM_COMMUNICATE_PORT, gabriel.control.UCommRelayHandler)

    ## publish servers that publish sensor streams to offloading engines (start with p_)
    p_video_server = gabriel.control.SensorPublishServer(gabriel.Const.PUBLISH_SERVER_VIDEO_PORT, gabriel.control.VideoPublishHandler)
    p_acc_server = gabriel.control.SensorPublishServer(gabriel.Const.PUBLISH_SERVER_ACC_PORT, gabriel.control.AccPublishHandler)
    p_audio_server = gabriel.control.SensorPublishServer(gabriel.Const.PUBLISH_SERVER_AUDIO_PORT, gabriel.control.AudioPublishHandler)

    ## create a thread for each server
    # mobile
    m_control_server_thread = threading.Thread(target = m_control_server.serve_forever)
    m_video_server_thread = threading.Thread(target = m_video_server.serve_forever)
    m_acc_server_thread = threading.Thread(target = m_acc_server.serve_forever)
    m_audio_server_thread = threading.Thread(target = m_audio_server.serve_forever)
    m_result_server_thread = threading.Thread(target = m_result_server.serve_forever)
    ucomm_relay_thread = threading.Thread(target = ucomm_relay_server.serve_forever)

    # publish
    p_video_server_thread = threading.Thread(target = p_video_server.serve_forever)
    p_acc_server_thread = threading.Thread(target = p_acc_server.serve_forever)
    p_audio_server_thread = threading.Thread(target = p_audio_server.serve_forever)

    # set daemon
    m_control_server_thread.daemon = True
    m_video_server_thread.daemon = True
    m_acc_server_thread.daemon = True
    m_audio_server_thread.daemon = True
    m_result_server_thread.daemon = True
    ucomm_relay_thread.daemon = True
    p_video_server_thread.daemon = True
    p_acc_server_thread.daemon = True
    p_audio_server_thread.daemon = True

    all_thread_list = [m_video_server_thread, m_acc_server_thread, m_audio_server_thread, m_result_server_thread, p_video_server_thread, p_acc_server_thread, p_audio_server_thread, ucomm_relay_thread]

    ## start!
    try:
        m_control_server_thread.start()
        m_video_server_thread.start()
        m_acc_server_thread.start()
        m_audio_server_thread.start()
        m_result_server_thread.start()
        ucomm_relay_thread.start()
        p_video_server_thread.start()
        p_acc_server_thread.start()
        p_audio_server_thread.start()

        while True:
            time.sleep(100)
    except KeyboardInterrupt as e:
        sys.stdout.write("Exit by user\n")
        sys.exit(0)
    except Exception as e:
        sys.stderr.write(str(e))
        sys.exit(1)
    finally:
        finish_background_services()
        if m_control_server is not None:
            m_control_server.terminate()
        if m_video_server is not None:
            m_video_server.terminate()
        if m_acc_server is not None:
            m_acc_server.terminate()
        if m_audio_server is not None:
            m_audio_server.terminate()
        if m_result_server is not None:
            m_result_server.terminate()
        if ucomm_relay_server is not None:
            ucomm_relay_server.terminate()
        if p_video_server is not None:
            p_video_server.terminate()
        if p_acc_server is not None:
            p_acc_server.terminate()
        if p_audio_server is not None:
            p_audio_server.terminate()

    '''
    for each_thread in all_thread_list:
        if each_thread.is_alive() == True:
            import pdb;pdb.set_trace()
    '''


if __name__ == '__main__':
    main()
