from steamvr.paths import steam_vr_path
import json
import os
from steamvr.unbuffered.calibration import ImuSolution
import tempfile
import subprocess
import psutil
import polling2
from enum import IntEnum


STEAM_DEVICE_EYE_STRING = ['eEYE_LEFT', 'eEYE_RIGHT']


class DeviceEye(IntEnum):
    left_eye = 0
    right_eye = 1


def load_steamvr_json(path: str) -> dict:
    assert os.path.exists(path)
    with open(path, 'r') as f:
        return json.load(f)


def save_steamvr_json(config: dict, file: str):
    with open(file, 'w') as f:
        json.dump(config, f, indent=4)


def load_steamvr_vrsettings(path=r'C:\Program Files (x86)\Steam\config\steamvr.vrsettings'):
    with open(path, 'r') as f:
        config = json.load(f)
    return config


def save_steamvr_vrsettings(config, path=r'C:\Program Files (x86)\Steam\config\steamvr.vrsettings'):
    with open(path, 'w') as f:
        json.dump(config, f, indent=4)


def is_vrcmd_running():
    return "vrcmd.exe" in (p.name() for p in psutil.process_iter())


def update_steam_real_time(config: dict):
    with tempfile.TemporaryDirectory() as temp_dir:
        temp_config = temp_dir + 'config.json'
        save_steamvr_json(config, temp_config)
        cmd = [steam_vr_path + 'vrcmd.exe', "--cmdhmd",
               "set_lenscal " + temp_config]
        p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
        polling2.poll(target=lambda: not is_vrcmd_running(), step=0.1, timeout=10)
        p.kill()
        p.wait(timeout=10)


def set_resolution(config, width, height):
    config['device']['eye_target_height_in_pixels'] = height
    config['device']['eye_target_width_in_pixels'] = width
    return config


def get_resolution(config):
    return config['device']['eye_target_width_in_pixels'],\
           config['device']['eye_target_height_in_pixels']


def set_serial_number(config, serial):
    config['device_serial_number'] = serial


def set_ipd_default_mm(config, ipd):
    config['ipd']['default_mm'] = ipd


def get_serial_number(config):
    return config['device_serial_number']


def set_eye_to_head(config, eye_to_head):
    assert 'eye_to_head' in config
    if eye_to_head.shape != (3, 3):
        raise ValueError("eye to head expects rotation")
    config['eye_to_head'] = eye_to_head.tolist()


def swap_xy_distortion_center(config):
    config['tracking_to_eye_transform'][0]['distortion']['center_x'], \
        config['tracking_to_eye_transform'][0]['distortion']['center_y'] = \
        config['tracking_to_eye_transform'][0]['distortion']['center_y'], \
        config['tracking_to_eye_transform'][0]['distortion']['center_x']

    config['tracking_to_eye_transform'][1]['distortion']['center_x'], \
        config['tracking_to_eye_transform'][1]['distortion']['center_y'] = \
        config['tracking_to_eye_transform'][1]['distortion']['center_y'], \
        config['tracking_to_eye_transform'][1]['distortion']['center_x']


def swap_eyes(config):
    config['tracking_to_eye_transform'][0],\
        config['tracking_to_eye_transform'][1] = \
        config['tracking_to_eye_transform'][1],\
        config['tracking_to_eye_transform'][0]

    config['device']['first_eye'], \
        config['device']['last_eye'] = \
        config['device']['last_eye'], \
        config['device']['first_eye']


def set_imu_calibration(config, imu: ImuSolution):
    config['imu']['acc_bias'] = imu.imu.acc_bias
    config['imu']['acc_scale'] = imu.imu.acc_scale
    config['imu']['gyro_bias'] = imu.imu.gyro_bias


class KillingOtherInstance(Exception):
    pass


class SteamVR:
    def __init__(self):
        self.started_by_me = False

    def _kill_instance(self):
        if not self.started_by_me:
            raise KillingOtherInstance("Warning, attempting to kill and instance "
                                       "that wasn't started by me, if intended, "
                                       "try except this exception")
        for p in psutil.process_iter():
            if p.name() == "vrmonitor.exe":
                p.kill()
                p.wait(timeout=10)
        for p in psutil.process_iter():
            if p.name() == "vrserver.exe":
                p.kill()
                p.wait(timeout=10)

    @staticmethod
    def is_vrmonitor_running() -> bool:
        return "vrmonitor.exe" in (p.name() for p in psutil.process_iter())

    def start(self):
        self.started_by_me = True
        self._kill_instance()  # kill any running instance
        p = subprocess.Popen([steam_vr_path + '/vrstartup.exe'])
        polling2.poll(target=self.is_vrmonitor_running, step=0.1, timeout=10)
        p.kill()

    def stop_external_instance(self):
        self.started_by_me = True
        self._kill_instance()
        self.started_by_me = False

    @staticmethod
    def create():
        vr = SteamVR()
        vr.start()
        return vr

    def stop(self):
        assert self.is_vrmonitor_running()
        self._kill_instance()
        self.started_by_me = False

    def set_ipd_offset(self, ipd_offset_mm):
        c = load_steamvr_vrsettings()
        c['steamvr']['ipdOffset'] = ipd_offset_mm / 1000
        save_steamvr_vrsettings(c)
        if self.is_vrmonitor_running():
            self.restart()

    def set_ipd(self, ipd_mm):
        default_ipd = 63
        self.set_ipd_offset(ipd_mm - default_ipd)

    def restart(self):
        if self.is_vrmonitor_running():
            self.stop()
        self.start()

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.stop()
