text: # -*- coding: utf-8 -*-
  import re
  import json
  import os
  import pytz
  import weakref
  from ast import literal_eval
  from .inactivity_checker import InactivityChecker
  from ..utils.constants import STATUS, RESULTS, JOB_PARAMS
  from ..utils.config import get_config, get_senders_from_config
  from ..utils.time import get_utc_offset, get_period_length
  from ..utils.query import get_api_client, launch_api_query, \
                            get_formatted_query, prepend_domain_to_query, \
                            prepend_period_filter_to_query, append_limit_to_query
  from ..utils.logging import merge_logging_confs, get_global_logger
  from ..utils.exception import LapuaJobException, LapuaConfigurationException
  from ..utils.constants import PERIOD_REGEX
  from ..utils.data import MODELS
  
  __author__ = 'Devo (Daniel Alonso)'
  
  
  MANDATORY_PARAMS = ['instance', 'domain', 'table', 'period',
                      'auth', 'name', 'created_by']
  MANDATORY_RELATED_PERIODS_PARAMS = ['periods', 'stdevs', 'amount']
  EVENTDATE_REGEX = re.compile('where.*[^\w]eventdate[^\w]')
  JOB_NAME_REGEX = re.compile('[^\w\d]')
  
  
  class Job:
      class Vigil(object):
          def __init__(self, id):
              self.id = id
  
      def __init__(self, path, cb):
          self._path = path
          self._status = STATUS.STOPPED
          self._thread = None
          self._cb = cb
  
          self._load_config()
  
      def run(self, reload_config=False):
          if self._thread:
              raise LapuaJobException("Thread is already running.")
  
          if reload_config:
              self._load_config()
  
          self._thread = InactivityChecker(self._config)
          self._start_watching()
          self._thread.start()
          self._status = STATUS.RUNNING
  
      def reload_config(self):
          self._load_config()
  
      def stop(self):
          if self._thread:
              if self._thread.is_checking():
                  raise LapuaJobException("Thread is currently checking.")
              else:
                  self._thread.stop()
  
          self._stop_watching()
          self._thread = None
          self._status = STATUS.STOPPED
  
      def destroy(self):
          if self._thread:
              raise LapuaJobException("Thread is alive.")
          else:
              self._status = STATUS.DELETED
              os.remove(self._path)
  
      def get_status(self):
          return self._status
  
      def get_config(self):
          return json.loads(self._raw_config)
  
      def check_health(self):
          if self._status == STATUS.RUNNING:
              try:
                  return 'ALIVE' if self._thread.is_alive() else 'DEAD'
              except:
                  return 'UNKNOWN'
          elif self._status == STATUS.STOPPED:
              return 'STOPPED'
          elif self._status == STATUS.DELETED:
              return 'DELETED'
  
      def test(self):
          query = prepend_domain_to_query(
              prepend_period_filter_to_query(
                  get_formatted_query(self._config), self._config['period']),
              self._config['domain'])
  
          response = launch_api_query(
              get_api_client(self._config['instance'],
                             self._path,
                             self._config['auth']),
              append_limit_to_query(query, 5),
              "now() - {}".format(
                  get_period_length(self._config['period']) * 2000),
              skip_headers=False)
  
          try:
              return {
                  "status": "OK",
                  "response": [r for r in response],
                  "query": query
              }
          except Exception as excpt:
              return {
                  "status": "ERROR",
                  "response": literal_eval(str(excpt)),
                  "query": query
              }
  
      def _load_config(self):
          try:
              with open(self._path) as file_desc:
                  config = json.load(file_desc)
              self._validate_job_configuration(config)
              self._raw_config = json.dumps(config)
              self._config = self._fill_job_parameters(config)
          except:
              raise
  
      def _fill_job_parameters(self, conf):
          config = get_config()
          conf['instance'] = config['api'][conf['instance']]
          conf['sender'] = get_senders_from_config(
              conf.get('send_to', config['targets']['monitor']))
          conf['logging'] = merge_logging_confs(
              get_global_logger(), conf.get('logging'))
          conf['offset'] = get_utc_offset(conf['timezone']) \
              if conf.get('timezone') else 0
          if not 'group_by' in conf:
              conf['group_by'] = None
          if 'related_periods' in conf and conf.get('related_periods') \
                  and conf['related_periods'].get('regression'):
              conf['related_periods']['regression'] = list(map(
                  lambda x: MODELS.MAPPER[x],
                  conf['related_periods']['regression']))
  
          return conf
  
      def _validate_job_configuration(self, conf):
          for param in MANDATORY_PARAMS:
              if not param in conf:
                  raise LapuaConfigurationException(
                      'Mandatory param "{}" not found.'.format(param))
  
          for param in conf:
              if param in JOB_PARAMS.TYPES and conf[param] \
                      and not isinstance(conf[param], JOB_PARAMS.TYPES[param]):
                  raise LapuaConfigurationException(
                      'Param "{}" must be of type {}'.format(
                          param, JOB_PARAMS.TYPES[param]))
  
          for param in conf['auth']:
              if param in JOB_PARAMS.AUTH_TYPES and conf['auth'][param] \
                      and not isinstance(
                          conf['auth'][param], JOB_PARAMS.AUTH_TYPES[param]):
                  raise LapuaConfigurationException(
                      'Param "auth.{}" must be of type {}'.format(
                          param, JOB_PARAMS.AUTH_TYPES[param]))
  
          if JOB_NAME_REGEX.findall(conf['name']):
              raise LapuaConfigurationException(
                  'Unsupported character/s in job name. '
                  'Only alphanumeric characters are allowed.')
  
          if conf.get('query') and EVENTDATE_REGEX.findall(conf['query']):
              raise LapuaConfigurationException(
                  'Query can\'t contain eventdate filters.')
  
          if conf.get('timezone') and not conf['timezone'] in pytz.all_timezones:
              raise LapuaConfigurationException(
                  'Unrecognized timezone "{}"'.format(conf['timezone']))
  
          config = get_config()
          if not conf['instance'] in config['api']:
              raise LapuaConfigurationException(
                  'Unrecognized api instance "{}". '
                  'Must be one of [{}]'.format(
                      conf['instance'], ', '.join(config['api'].keys())))
  
          if not PERIOD_REGEX.findall(conf['period']):
              raise LapuaConfigurationException(
                  'Unrecognized period format "{}"'.format(conf['period']))
  
          if conf.get('related_periods'):
              for param in MANDATORY_RELATED_PERIODS_PARAMS:
                  if not param in conf['related_periods']:
                      raise LapuaConfigurationException(
                          'Mandatory param "related_periods.{}" not found.'.format(
                              param))
              for param in conf['related_periods']:
                  if param in JOB_PARAMS.RELATED_PERIODS_TYPES \
                          and conf['related_periods'][param] \
                          and not isinstance(
                              conf['related_periods'][param],
                              JOB_PARAMS.RELATED_PERIODS_TYPES[param]):
                      raise LapuaConfigurationException(
                          'Param "related_periods.{}" must be of type {}'.format(
                              param, JOB_PARAMS.RELATED_PERIODS_TYPES[param]))
                  if param == 'regression':
                      if len(conf['related_periods'][param]) > 4:
                          raise LapuaConfigurationException(
                              'Param "related_periods.{}" must be of length <= 4')
                      for regression in conf['related_periods'][param]:
                          if not regression in MODELS.MAPPER:
                              raise LapuaConfigurationException(
                                  'Unrecognized regression "{}"'.format(
                                      regression))
  
              if not PERIOD_REGEX.findall(conf['period']):
                  raise LapuaConfigurationException(
                      'Unrecognized related period format "{}"'.format(
                          conf['related_periods']['periods']))
  
      def _start_watching(self):
          self._thread.ref = Job.Vigil(id(self._thread))
          self._ref = weakref.ref(self._thread.ref, self._watcher_cb)
  
      def _stop_watching(self):
          self._ref = None
  
      def _watcher_cb(self, ref_id):
          result = self._thread.execution_result
          self.stop()
  
          if result == RESULTS.STARTUP_ERROR:
              self._cb()
          elif result == RESULTS.EXECUTION_ERROR:
              self.run()
  