# Copyright (C) 2023 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import obsws_python as obs
import time
from contextlib import suppress
import websocket
import queue

import logging
obws_logger = logging.getLogger("obsws_python.baseclient")
obws_logger.setLevel(logging.CRITICAL)

logger = logging.getLogger(__name__)


class OBSInput:

   def __init__(self, inp, config_section, obss):
      self.config_section = config_section
      self.inp = inp
      self.status = False
      self.obss = obss
      self.config_options = config.options(config_section)
      self.config_section = config_section
      for c in self.config_options:
         setattr(self, c, config.get(config_section, c))

   def getStatus(self):
      return self.obss.getInputStatus(self.scene, self.source)

   def updateStatus(self):
      status = self.getStatus()
      if status != self.status:
         self.status = status
         logging.info('INPUT '+self.inp+" FOUND "+str(self.status))
         self.obss.queue.put({ 'event': 'INPUTSTATUSCHANGE', 'data': {'server': self.obss.server, 'input': self.inp, 'status': self.status }})


class OBSOutput:

   def __init__(self, output, config_section, obss):
      self.config_section = config_section
      self.output = output
      self.status = False
      self.obss = obss
      self.config_options = config.options(config_section)
      self.config_section = config_section
      self.statuses = {}
      self.inputs = {}
      self.inpmon = []
      for c in self.config_options:
         if 'status.' in c:
            for inp in config.get(config_section, c).split(','):
               if inp not in self.inputs.keys():
                  self.inputs[inp] = False
            if c.split('.')[1] not in self.statuses.keys():
               enabled=[]
               disanled=[]
               if config.get(config_section, "status."+c.split(".")[1]+".disable", fallback=False):
                  disabled=config.get(config_section, "status."+c.split(".")[1]+".disable", fallback=False).split(',')
               if config.get(config_section, "status."+c.split(".")[1]+".enable", fallback=False):
                  enabled=config.get(config_section, "status."+c.split(".")[1]+".enable", fallback="").split(',')
               self.statuses[c.split('.')[1]] = {'disable': disabled,
                                                 'enable': enabled,
                                                 }

         setattr(self, c, config.get(config_section, c))
      logging.debug(self.statuses)
      logging.debug(self.inputs)
      #print(getattr(self, 'source.closed'))
      #print(dir(self))

   def disable(self, sources):
      pass

   def enable(self, sources):
      pass

   def setStatus(self, status):
      if hasattr(self, 'status.'+status+'.disable'):
         self.disable(getattr(self, 'status.'+status+'.disable').split(','))

      if hasattr(self, 'status.'+status+'.enable'):
         self.enable(getattr(self, 'status.'+status+'.enable').split(','))

   def _updateStatus(self):
      if not self.obss.online:
         return False
      for inp in self.inputs.keys():
         self.inputs[inp] = self.obss.getInputStatus(self.scene, inp)
      found=False
      for status  in self.statuses.keys():
         if not found:
            found = status
            for enabled in self.statuses[status]['enable']:
               if not self.inputs[enabled]:
                  found = False
            if found:
               for disabled in self.statuses[status]['disable']:
                  if self.inputs[disabled]:
                     found = False
            if found:
               return found
      return found

   def updateStatus(self):
      status = self._updateStatus()
      if self.status != status:
         self.status = status
         logging.info('OUTPUT '+self.output+" FOUND "+str(self.status))
         self.obss.queue.put({ 'event': 'OUTPUTCHANGE', 'data': {'server': self.obss.server, 'output': self.output, 'status': self.status }})
      return self.status

   def getStatus(self):
      if not self.status:
         self.status = self.updateStatus()
      return self.status

   def getInput(self, inp):
      if inp in self.inputs.keys():
         return self.obss.getInputStatus(self.scene, inp)
      return False

class OBSControl:

   def __init__(self, obs_server, config_section):
      self.online = False
      self.last_try = time.time()-11
      self.lastping = time.time()-20
      self.server = obs_server
      self.queue = queue.Queue()
      self.config_section = config_section
      self.config_options = config.options(config_section)
      self.cl = False
      self.cr = False
      for c in self.config_options:
         setattr(self, c, config.get(config_section, c))

   def start(self):
      if self.online:
         return
      self.last_try = time.time()
      try:
         self.cl = obs.EventClient(host=self.host, port=self.port, password=self.password)
         self.cr = obs.ReqClient(host=self.host, port=self.port, password=self.password)
         self.cl.callback.register(self.on_scene_item_enable_state_changed)
         self.setonline()
      except:
         pass

   def getInputStatus(self, scene, inp):
      if self.cr:
         return self.cr.get_scene_item_enabled(scene, int(inp)).scene_item_enabled
      return False

   def setonline(self):
      logging.info('OBS '+self.server+' ONLINE')
      self.online = True
      self.queue.put({ 'event': 'SETONLINE', 'data': {'server': self.server}})

   def setoffline(self):
      self.online = False
      self.queue.put({'event': 'SETOFFLINE', 'data': {'server': self.server}})

   def run_tasks(self):
      if not self.online and time.time()-self.last_try > 10:
         logging.info("OBS "+self.server+" starting... ")
         self.start()

      if self.online:
         if time.time()-self.lastping > 20:
            try:
               self.cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}})
               self.lastping = time.time()
               logging.info("SENT Ping to "+self.server)
            except:
               self.setoffline()


   def on_scene_item_enable_state_changed(self, data):
      logging.info("scscene_item_enable_state_changed")
      logging.info(data.attrs())
      logging.info([data.scene_item_enabled, data.scene_item_id, data.scene_name, data.scene_uuid])
      self.queue.put({'event': 'INPUTCHANGE', 'data': {'server': self.server, 'status': data.scene_item_enabled, 'scene':  data.scene_name, 'source': data.scene_item_id}})


def run_obs_controller():
    obs_servers = {}
    for k in [x for x in config.sections() if 'OBS:' in x]:
        #if not config.get(k, 'active', fallback=False):
        obs_servers[k.split(":",1)[1]] = OBSControl(k.split(":",1)[1], k)


    obs_outputs = {}
    for k in [x for x in config.sections() if 'OUTPUT:' in x]:
        obss = False
        if config.get(k, 'obs') in obs_servers.keys():
           obss = obs_servers[config.get(k, 'obs')]
        obs_outputs[k.split(":",1)[1]] = OBSOutput(k.split(":",1)[1], k, obss )
	    


        logging.info('OUTPUT '+config.get(k, 'obs')+" -> "+k.split(":",1)[1])
        logging.info(obs_outputs[k.split(":",1)[1]].getStatus())


    obs_inputs = {}
    for k in [x for x in config.sections() if 'INPUT:' in x]:
       obss = False
       if config.get(k, 'obs') in obs_servers.keys():
          obss = obs_servers[config.get(k, 'obs')]
       obs_inputs[k.split(":",1)[1]] = OBSInput(k.split(":",1)[1], k, obss )

    #cl = obs.EventClient(host='192.168.42.115', port=4455, password='motorol4')
    #cr = obs.ReqClient(host='192.168.42.115', port=4455, password='motorol4')

    #cl.callback.register(on_scene_item_enable_state_changed)
    
    #print(cl.callback.get())

    #cl.callback.deregister(on_input_mute_state_changed)

    now=time.time()-30
    while True:
        if not qobs.empty():
            task=qobs.get(block=True)
            if task:
                logging.info('TASK INCOMING FOR OBS')
                logging.info(task)
        if (time.time() - now) > 30:
            now = time.time()
            #cr.broadcast_custom_event({'eventData': {'eventType': 'Ping', 'time': time.time()}})
        for o in obs_servers.keys():
            obs_servers[o].run_tasks()
            if not obs_servers[o].queue.empty():
               task=obs_servers[o].queue.get(block=True)
               if task:
                  logging.info('EVENT COMING FROM OBS '+o)
                  logging.info(task)
                  event = task['event']
                  data = task['data']
                  if event == 'SETOFFLINE' or event == 'SETONLINE':
                     for output in obs_outputs.values():
                        if output.obss.server == data['server']:
                           output.updateStatus()
                  if event == 'INPUTCHANGE':
                     #'data': {'server': 'leeloo', 'status': False, 'scene': 'LIVE SFW', 'source': 5}}
                     for output in obs_outputs.values():
                        if output.obss.server == data['server'] and output.scene==data['scene'] and str(data['source']) in output.inputs.keys():
                           output.updateStatus()
                     for inp in obs_inputs.values():
                        if inp.obss.server == data['server'] and inp.scene==data['scene'] and str(data['source'])==str(inp.source):
                           inp.updateStatus()
                  if event == 'INPUTSTATUSCHANGE':
                     logging.info("INPUTSTATUSCHANGE: input "+data['input']+ " on obs "+data['server']+" is now "+str(data['status']))                  
                     qcore.put(task)
                  if event == 'OUTPUTCHANGE':
                     #{ 'event': 'OUTPUTCHANGE', 'data': {'server': self.obss.server, 'output': self.output, 'status': self.status }}
                     logging.info("OUTPUTCHANGE: output "+data['output']+ " on obs "+data['server']+" is now "+str(data['status']))
                     qcore.put(task)


        time.sleep(.01)

