import json
import socket
import os
import threading
import time
import io
import select
import netifaces as ni

from msgids import *


class Session():
    pass


class MiSphereConn:
    ms_ip = '192.168.42.1'
    ms_tcp_port = 7878
    ms_uk_port = 8787
    ms_fs_port = 50422

    def init(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.socket.connect((self.ms_ip, self.ms_tcp_port))
        self.socket_1 = socket.socket()
        self.recv_handle_live = True
        self.recv_thread = threading.Thread(target=self.recv_handler)
        self.recv_thread.daemon = True
        self.session = Session()
        self.session.conf = {}
        self.session.locks = {}
        self.last_send = 0

    def handle_data(self, func):
        self.handle_data = func

    def recv_handler(self):
        while self.recv_handle_live:
            r, _, _ = select.select([self.socket], [], [], 0.5)
            if r:
                resps = self.socket.recv(512).decode()
                while True:
                    resp_dict, idx = json.JSONDecoder().raw_decode(resps)
                    self.resp_handler(resp_dict)
                    if idx >= len(resps):
                        break
                    else:
                        resps = resps[idx:]

    def resp_handler(self, data):
        if 'rval' in data:
            print(data)
            # assert(data['rval'] >= 0)
        self.handle_data(data)
        if data['msg_id'] == CONNECT:
            self.session.token = data['param']
        elif data['msg_id'] == DISCONNECT:
            self.recv_handle_live = False
        elif data['msg_id'] in [GET_CAMERA, GET_STORAGE, GET_DEVICE]:
            cdata = {}
            for e in data:
                if e not in ['rval', 'msg_id']:
                    cdata[e] = data[e]
            self.session.conf[data['msg_id']] = cdata
        else:
            print("UNKNOWN", data)
        if data['msg_id'] in self.session.locks:
            self.session.locks[data['msg_id']].release()
            print('[UNLOCK]', data['msg_id'])

    def connect_8787(self):
        self.socket_1.connect((self.ms_ip, self.ms_uk_port))

    def poweroff(self):
        self.send(TURN_OFF)

    def create_payload(self, msg_id, token, params=None):
        payload = dict()
        payload['msg_id'] = msg_id
        if params is not None:
            for k, v in params.items():
                payload[k] = str(v) if k == 'param' else v
        payload['token'] = token
        return payload

    def send(self, msg_id, params=None, token=None):
        if msg_id not in self.session.locks:
            self.session.locks[msg_id] = threading.Lock()
        print("[LOCK] ", msg_id)
        if self.session.locks[msg_id].locked():
            return 0
        else:
            self.session.locks[msg_id].acquire()
        if token is None:
            token = self.session.token
        payload = self.create_payload(msg_id, token, params)
        data = json.dumps(payload).encode()
        print(data)
        timediff = time.time() - self.last_send
        if timediff < 0.5:
            time.sleep(0.5 - timediff)
        self.last_send = time.time()
        return self.socket.send(data)

    def recv(self):
        response = self.socket.recv(1024)
        response = json.loads(response.decode())
        return response

    def send_recv(self, msg_id, params=None, token=None):
        self.send(msg_id, params, token)
        return self.recv()

    def get_mode(self):
        self.send(GET_CAMERA)

    def connect(self):
        self.recv_handle_live = True
        self.recv_thread.start()
        self.send(CONNECT, token=0)
        while self.session.locks[CONNECT].locked():
            time.sleep(.2)
        print('token is {}'.format(self.session.token))
        self.get_details()

    def set_sub_camera(self, sub_cam, value=0):
        if sub_cam == 'Timer':
            self.send(SET_PHOTO_TIMER, {'param': value})
        elif sub_cam == 'Intervalometer':
            self.send(SET_PHOTO_INTERVALOMETER, {'param': value})
        elif sub_cam == 'Bracketing':
            self.send(SET_PHOTO_BRACKETING, {'param': int(2 * value)})
        elif sub_cam == 'None':
            self.send(SET_PHOTO_TIMER, {'param': 0})
            self.send(SET_PHOTO_INTERVALOMETER, {'param': 0})
            self.send(SET_PHOTO_BRACKETING, {'param': 0})

    def set_sub_video(self, sub_vid, value=0):
        if sub_vid == 'Short video':
            self.send(SET_VIDEO_SHORT, {'param': value})
        elif sub_vid == 'Timelapse':
            self.send(SET_VIDEO_TIMELAPSE, {'param': value})
        elif sub_vid == 'Slow motion':
            self.send(SET_VIDEO_SLOWMO, {'param': value})
        elif sub_vid == 'None':
            self.send(SET_VIDEO_SHORT, {'param': 0})
            self.send(SET_VIDEO_TIMELAPSE, {'param': 0})
            self.send(SET_VIDEO_SLOWMO, {'param': 0})

    def get_details(self):
        self.get_camera_details()
        self.get_storage_details()
        self.get_device_details()

    def is_set_to_photo(self):
        return self.send(GET_CAMERA)['mode'] == 1

    def is_set_to_video(self):
        return self.send(GET_CAMERA)['mode'] == 0

    def switch_to_video(self):
        return self.send(SWITCH_MODE, {'param': 0})

    def switch_to_photo(self):
        return self.send(SWITCH_MODE, {'param': 1})

    def switch_mode(self):
        if self.is_set_to_photo():
            return self.switch_to_video()
        elif self.is_set_to_video():
            return self.switch_to_photo()

    def get_camera_details(self):
        return self.send(GET_CAMERA)

    def get_storage_details(self):
        return self.send(GET_STORAGE)

    def get_device_details(self):
        return self.send(GET_DEVICE)

    def set_photo_EV(self, value):
        return self.send(PHOTO_EV, {'param': int(2 * value)})

    def set_photo_ISO(self, value):
        return self.send(PHOTO_ISO, {'param': value})

    def set_photo_WB(self, value):
        return self.send(PHOTO_WB, {'param': value})

    def get_info(self, filepath=''):
        self.send(FILE_INFO,
                  {
                      "param": "/tmp/SD0/DCIM/20171210/f0297728.jpg",
                  }
                  )
        return self.recv()

    def get_thumb(self, filepath=''):
        self.send(FILE_PREVIEW,
                  {
                      "param": "/tmp/SD0/DCIM/20171210/f0297728.jpg",
                      "type": "thumb"
                  }
                  )
        return self.recv()

    def register_tcp(self, params={}):
        for e in ni.interfaces():
            if e.lower().startswith('w'):
                ip = ni.ifaddresses(e)[ni.AF_INET][0]['addr']
                if ip.startswith("192.168.42."):
                    break
        params = {"param": ip, "type": "TCP"}
        self.send(REGISTER_TCP, params)
        return self.recv()

    def click_picture(self, long_press=False):
        self.send(PHOTO_LONGPRESS if long_press else PHOTO_PRESS)

    def record_video(self, long_press=False):
        self.send(VIDEO_LONGPRESS if long_press else VIDEO_REC_START)

    def record_stop(self):
        self.send(VIDEO_REC_STOP)

    def change_camera_res(self, value):
        l = {
            '6912x3456': 0,
            '3456x1728 (stitched)': 1,
            '6912x3456 (stitched)': 2,
            '6912x3456 with RAW': 3
        }
        self.send(PHOTO_RES, {'param': l[value]})

    def change_camera_wb(self, value):
        l = {
            'Auto': 0,
            'Outdoors': 1,
            'Overcast': 2,
            'Incandescent': 3,
            'Fluorescent': 4
        }
        self.send(PHOTO_WB, {'param': l[value]})

    def change_camera_iso(self, value):
        l = {
            'Auto': 0,
            '50': 50,
            '100': 100,
            '200': 200,
            '400': 400,
            '800': 800,
            '1600': 1600
        }
        self.send(PHOTO_ISO, {'param': l[value]})

    def change_camera_shutter(self, value):
        print("yo")

        def l(val):
            if val == 'Auto':
                return 0
            else:
                if val.startswith('1/'):
                    val = val[2:]
                    val = int(val)
                    val += 1 << 15
                else:
                    val = int(val)
            return val
        self.send(PHOTO_SHUTTERTIME, {'param': l(value)})

    def change_camera_ev(self, value):
        self.send(PHOTO_EV, {'param': int(2 * value)})

    def change_video_res(self, value):
        l = {
            '3456x1728 | 30fps': 0,
            '2304x1152 | 30fps': 1,
            '2304x1152 | 60fps': 2,
            '2048x512 | 120fps': 3,
            '2048x512 | 120fps bottom (Bullet time)': 4,
            '3456x1728 | 30fps HighBitrate': 5
        }
        self.send(VIDEO_RES, {'param': l[value]})

    def change_video_wb(self, value):
        l = {
            'Auto': 0,
            'Outdoors': 1,
            'Overcast': 2,
            'Incandescent': 3,
            'Fluorescent': 4
        }
        self.send(VIDEO_WB, {'param': l[value]})

    def change_video_ev(self, value):
        self.send(VIDEO_EV, {'param': int(2 * value)})

    def set_auto_off(self, value):
        self.send(AUTO_TURN_OFF, {'param': value})

    def set_buzzer_volume(self, value):
        self.send(SET_BUZZER_VOLUME, {'param': value})

    def set_led(self, value):
        self.send(SET_LED, {'param': int(value)})

    def disconnect(self):
        self.send(DISCONNECT)
        while self.session.locks[DISCONNECT].locked():
            time.sleep(0.5)
        self.recv_handle_live = False
        self.recv_thread.join()

    def test(self, l, h):
        import msgids
        known = set()
        for e in vars(msgids):
            if not e.startswith('__'):
                known.add(msgids.__dict__[e])
        for i in range(l, h + 1):
            self.send(i)

    def __del__(self):
        self.socket.close()
        self.socket_1.close()


if __name__ == '__main__':
    cam = MiSphereConn()
    cam.connect()
    # cam.click_picture()
    # cam.disconnect()