import os
import hydra
from abc import ABC
from .recorders.image import RGBImageRecorder, DepthImageRecorder, FishEyeImageRecorder
from .recorders.robot_state import RobotInformationRecord
from .recorders.sim_state import SimInformationRecord
from .recorders.sensors import XelaSensorRecorder
from .sensors import *
#from .sensors.sim_cam import *
from multiprocessing import Process
from openteach.constants import *
#
# from multiprocessing import set_start_method
#import multiprocessing


class ProcessInstantiator(ABC):
    def __init__(self, configs):
        self.configs = configs
        self.processes = []

    def _start_component(self,configs):
        raise NotImplementedError('Function not implemented!')

    def get_processes(self):
        return self.processes


class RealsenseCameras(ProcessInstantiator):
    """
    Returns all the camera processes. Start the list of processes to start
    the camera stream.
    """
    def __init__(self, configs):
        super().__init__(configs)
        # Creating all the camera processes
        self._init_camera_processes()

    def _start_component(self, cam_idx):
        component = RealsenseCamera(
            stream_configs = dict(
                host = self.configs.host_address,
                port = self.configs.cam_port_offset + cam_idx
            ),
            cam_serial_num = self.configs.robot_cam_serial_numbers[cam_idx],
            cam_id = cam_idx + 1,
            cam_configs = self.configs.cam_configs,
            stream_oculus = True if self.configs.oculus_cam == cam_idx else False
        )
        component.stream()

    def _init_camera_processes(self):
        for cam_idx in range(len(self.configs.robot_cam_serial_numbers)):
            self.processes.append(Process(
                target = self._start_component,
                args = (cam_idx, )
            ))

class FishEyeCameras(ProcessInstantiator):
    """
    Returns all the fish eye camera processes. Start the list of processes to start
    the camera stream.
    """
    def __init__(self, configs):
        super().__init__(configs)
        # Creating all the camera processes
        self._init_camera_processes()

    def _start_component(self, cam_idx):
        print('cam_idx: {}, stream_oculus: {}'.format(cam_idx, True if self.configs.oculus_cam == cam_idx else False))
        component = FishEyeCamera(
            cam_index=self.configs.fisheye_cam_numbers[cam_idx],
            stream_configs = dict(
                host = self.configs.host_address,
                port = self.configs.fish_eye_cam_port_offset+ cam_idx,
                set_port_offset = self.configs.fish_eye_cam_port_offset 
            ),
            
            stream_oculus = True if self.configs.stream_oculus and self.configs.oculus_cam == cam_idx else False,
            
        )
        component.stream()

    def _init_camera_processes(self):
        for cam_idx in range(len(self.configs.fisheye_cam_numbers)):
            self.processes.append(Process(
                target = self._start_component,
                args = (cam_idx, )
            ))


    

class TeleOperator(ProcessInstantiator):
    """
    Returns all the teleoperation processes. Start the list of processes 
    to run the teleop.
    """
    def __init__(self, configs):
        super().__init__(configs)
        #self.ctx = multiprocessing.get_context("spawn")
      
       
        
        if configs.sim_env:
            self._init_sim_environment()
        
        self._init_detector()
        self._init_keypoint_transform()
        self._init_visualizers()
        #if self.configs.robot.environment is not None:
            #self._init_sim_environment()


        if configs.operate: # To run the detector only
            self._init_operator()
        
        

        #if configs.debug_thumb:
            #self._init_debuggers()



    def _start_component(self, configs):
        
            
        component = hydra.utils.instantiate(configs)
        component.stream()

    def _start_sim_operator_component(self, configs):
        
        component = hydra.utils.instantiate(configs)
        component.stream()

    def _init_detector(self):
       
        self.processes.append(Process(
            target = self._start_component,
            args = (self.configs.robot.detector, )
        ))

    def _init_sim_environment(self):
         for env_config in self.configs.robot.environment:
            self.processes.append(Process(
                target = self._start_component,
                args = (env_config, )
            ))

    def _init_keypoint_transform(self):
        
        for transform_config in self.configs.robot.transforms:
            self.processes.append(Process(
                target = self._start_component,
                args = (transform_config, )
            ))

    def _init_visualizers(self):
       
        for visualizer_config in self.configs.robot.visualizers:
            self.processes.append(Process(
                target = self._start_component,
                args = (visualizer_config, )
            ))
        # XELA visualizer
        if self.configs.run_xela:
            for visualizer_config in self.configs.xela_visualizers:
                self.processes.append(Process(
                    target = self._start_component,
                    args = (visualizer_config, )
                ))


    def _init_operator(self):
       
        for operator_config in self.configs.robot.operators:
            #self.op=1
            #print(operator_config)
            #operator_config=hydra.utils.instantiate(operator_config,env=self.env)
            #operator_config['env']=self.env
            #print(operator_config)
            self.processes.append(Process(
                target = self._start_sim_operator_component,
                args = (operator_config, )

            ))
        
        #self.op=0
        #self.processes.append(ctx.Process(target=self._start_component,args= ({'env':self.allegroenv,})))

    def _init_debuggers(self):
        for debug_config in self.configs.robot.debuggers: # NOTE: There is only one anyways
            print(f'debug_config in initializers: {debug_config}')
            self.processes.append(Process(
                target = self._start_component,
                args = (debug_config,)
            ))

    

class Collector(ProcessInstantiator):
    """
    Returns all the recorder processes. Start the list of processes 
    to run the record data.
    """
    def __init__(self, configs, demo_num):
        super().__init__(configs)
        self.demo_num = demo_num
        self._storage_path = os.path.join(
            self.configs.storage_path, 
            'demonstration_{}'.format(self.demo_num)
        )
       
        self._create_storage_dir()
        self._init_camera_recorders()
        # Initializing the recorders
        if self.configs.sim_env is True:
            self._init_sim_recorders()
        else:
            print("Initialising robot recorders")
            self._init_robot_recorders()
        
        
        if self.configs.is_xela is True:
            self._init_sensor_recorders()

    def _create_storage_dir(self):
        if os.path.exists(self._storage_path):
            return 
        else:
            os.makedirs(self._storage_path)

    def _start_component(self, component):
        component.stream()

    


    # Obtaining the rgb and depth components
    def _start_rgb_component(self, cam_idx=0):
        if self.configs.sim is False:
            component = RGBImageRecorder(
                host = self.configs.host_address,
                image_stream_port = self.configs.cam_port_offset + cam_idx,
                storage_path = self._storage_path,
                filename = 'cam_{}_rgb_video'.format(cam_idx)
            )
        else:
            print("Reaching correct function")
            component = RGBImageRecorder(
            host = self.configs.host_address,
            image_stream_port = self.configs.sim_image_port+ cam_idx,
            storage_path = self._storage_path,
            filename = 'cam_{}_rgb_video'.format(cam_idx)
        )
        component.stream()

    def _start_depth_component(self, cam_idx):
        if self.configs.sim is not True:
            component = DepthImageRecorder(
                host = self.configs.host_address,
                image_stream_port = self.configs.cam_port_offset + cam_idx + DEPTH_PORT_OFFSET,
                storage_path = self._storage_path,
                filename = 'cam_{}_depth'.format(cam_idx)
            )
        else:
            component = DepthImageRecorder(
                host = self.configs.host_address,
                image_stream_port = self.configs.sim_image_port + cam_idx + DEPTH_PORT_OFFSET,
                storage_path = self._storage_path,
                filename = 'cam_{}_depth'.format(cam_idx)
            )
        component.stream()

    def _init_camera_recorders(self):
        if self.configs.sim is not True:
            print("Camera recorder starting")
            for cam_idx in range(len(self.configs.robot_cam_serial_numbers)):
                #print(cam_idx)
                self.processes.append(Process(
                    target = self._start_rgb_component,
                    args = (cam_idx, )
                ))

                self.processes.append(Process(
                    target = self._start_depth_component,
                    args = (cam_idx, )
                ))
        else:
          
            for cam_idx in range(1): #For now one camera
                self.processes.append(Process(
                    target = self._start_rgb_component,
                    args = (cam_idx, )
                ))

                self.processes.append(Process(
                    target = self._start_depth_component,
                    args = (cam_idx, )
                ))

    def _init_sim_recorders(self):
        port_configs = self.configs.robot.port_configs
        for key in self.configs.robot.recorded_data[0]:
            self.processes.append(Process(
                        target = self._start_sim_component,
                        args = (port_configs[0],key)))

    def _start_xela_component(self,
        controller_config
    ):
        component = XelaSensorRecorder(
            controller_configs=controller_config,
            storage_path=self._storage_path
        )
        component.stream()

        
    def _init_sensor_recorders(self):
        """
        For the XELA sensors and maybe microphones?
        """
        for controller_config in self.configs.robot.xela_controllers:
            self.processes.append(Process(
                target = self._start_xela_component,
                args = (controller_config, )
            ))

    def _start_fish_eye_component(self, cam_idx):
        component = FishEyeImageRecorder(
            host = self.configs.host_address,
            image_stream_port = self.configs.fish_eye_cam_port_offset + cam_idx,
            storage_path = self._storage_path,
            filename = 'cam_{}_fish_eye_video'.format(cam_idx)
        )
        component.stream()



    def _start_robot_component(
        self, 
        robot_configs, 
        recorder_function_key
    ):
        component = RobotInformationRecord(
            robot_configs = robot_configs,
            recorder_function_key = recorder_function_key,
            storage_path = self._storage_path
        )

        component.stream()

    def _start_sim_component(self,port_configs, recorder_function_key):
        #print("Host Address:",port_configs)

        component = SimInformationRecord(
                   port_configs = port_configs,
                   recorder_function_key= recorder_function_key,
                   storage_path=self._storage_path
        )
        component.stream()

    def _init_robot_recorders(self):
        # Instantiating the robot classes
        for idx, robot_controller_configs in enumerate(self.configs.robot.controllers):
            for key in self.configs.robot.recorded_data[idx]:
                self.processes.append(Process(
                    target = self._start_robot_component,
                    args = (robot_controller_configs, key, )
                ))


    

   