from collections import OrderedDict
from datetime import datetime
import logging.config
import os
import threading

import SimpleITK as sitk
import six

import radiomics

caseLogger = logging.getLogger('radiomics.script')
_parallel_extraction_configured = False


def extractVoxel(case_idx, case, extractor, **kwargs):
  global caseLogger

  out_dir = kwargs.get('out_dir', None)
  unix_path = kwargs.get('unix_path', False)

  # Instantiate the output
  feature_vector = OrderedDict(case)

  try:
    if out_dir is None:
      out_dir = '.'
    elif not os.path.isdir(out_dir):
      caseLogger.debug('Creating output directory at %s' % out_dir)
      os.makedirs(out_dir)

    caseLogger.info('Processing case %s', case_idx)
    t = datetime.now()

    imageFilepath = case['Image']  # Required
    maskFilepath = case['Mask']  # Required
    label = case.get('Label', None)  # Optional
    if isinstance(label, six.string_types):
      label = int(label)
    label_channel = case.get('Label_channel', None)  # Optional
    if isinstance(label_channel, six.string_types):
      label_channel = int(label_channel)

    # Extract features
    result = extractor.execute(imageFilepath, maskFilepath, label, label_channel, voxelBased=True)

    for k in result:
      if isinstance(result[k], sitk.Image):
        target = os.path.join(out_dir, 'Case-%i_%s.nrrd' % (case_idx, k))
        sitk.WriteImage(result[k], target, True)
        if unix_path and os.path.sep != '/':
          target = target.replace(os.path.sep, '/')
        feature_vector[k] = target
      else:
        feature_vector[k] = result[k]

    # Display message
    delta_t = datetime.now() - t
    caseLogger.info('Case %s processed in %s', case_idx, delta_t)

  except (KeyboardInterrupt, SystemExit):  # Cancel extraction by forwarding this 'error'
    raise
  except SystemError:
    # Occurs when Keyboard Interrupt is caught while the thread is processing a SimpleITK call
    raise KeyboardInterrupt()
  except Exception:
    caseLogger.error('Feature extraction failed!', exc_info=True)

  return feature_vector


def extractVoxel_parallel(args, logging_config=None, **kwargs):
  try:
    # set thread name to patient name
    threading.current_thread().name = 'case %s' % args[0]  # args[0] = case_idx

    if logging_config is not None:
      _configureParallelExtraction(logging_config)

    return extractVoxel(*args, **kwargs)
  except (KeyboardInterrupt, SystemExit):
    # Catch the error here, as this represents the interrupt of the child process.
    # The main process is also interrupted, and cancellation is further handled there
    return None


def _configureParallelExtraction(logging_config, add_info_filter=True):
  """
  Initialize logging for parallel extraction. This needs to be done here, as it needs to be done for each thread that is
  created.
  """
  global _parallel_extraction_configured
  if _parallel_extraction_configured:
    return

  # Configure logging
  ###################

  logging.config.dictConfig(logging_config)

  if add_info_filter:
    # Define filter that allows messages from specified filter and level INFO and up, and level WARNING and up from
    # other loggers.
    class info_filter(logging.Filter):
      def __init__(self, name):
        super(info_filter, self).__init__(name)
        self.level = logging.WARNING

      def filter(self, record):
        if record.levelno >= self.level:
          return True
        if record.name == self.name and record.levelno >= logging.INFO:
          return True
        return False

    # Adding the filter to the first handler of the radiomics logger limits the info messages on the output to just
    # those from radiomics.script, but warnings and errors from the entire library are also printed to the output.
    # This does not affect the amount of logging stored in the log file.
    outputhandler = radiomics.logger.handlers[0]  # Handler printing to the output
    outputhandler.addFilter(info_filter('radiomics.script'))

  # Ensure the entire extraction for each cases is handled on 1 thread
  ####################################################################

  sitk.ProcessObject_SetGlobalDefaultNumberOfThreads(1)

  _parallel_extraction_configured = True
  radiomics.logger.debug('parallel extraction configured')
