text: # Copyright (c) 2013-2021 khal contributors
  #
  # Permission is hereby granted, free of charge, to any person obtaining
  # a copy of this software and associated documentation files (the
  # "Software"), to deal in the Software without restriction, including
  # without limitation the rights to use, copy, modify, merge, publish,
  # distribute, sublicense, and/or sell copies of the Software, and to
  # permit persons to whom the Software is furnished to do so, subject to
  # the following conditions:
  #
  # The above copyright notice and this permission notice shall be
  # included in all copies or substantial portions of the Software.
  #
  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  #
  
  import glob
  import logging
  import os
  from os.path import expanduser, expandvars, join
  
  import pytz
  import xdg
  from tzlocal import get_localzone
  from validate import VdtValueError
  
  from ..khalendar.vdir import CollectionNotFoundError, Vdir
  from ..parse_datetime import guesstimedeltafstr
  from ..terminal import COLORS
  from .exceptions import InvalidSettingsError
  
  logger = logging.getLogger('khal')
  
  
  def is_timezone(tzstring):
      """tries to convert tzstring into a pytz timezone
  
      raises a VdtvalueError if tzstring is not valid
      """
      if not tzstring:
          return get_localzone()
      try:
          return pytz.timezone(tzstring)
      except pytz.UnknownTimeZoneError:
          raise VdtValueError(f"Unknown timezone {tzstring}")
  
  
  def is_timedelta(string):
      try:
          return guesstimedeltafstr(string)
      except ValueError:
          raise VdtValueError(f"Invalid timedelta: {string}")
  
  
  def weeknumber_option(option):
      """checks if *option* is a valid value
  
      :param option: the option the user set in the config file
      :type option: str
      :returns: off, left, right
      :rtype: str/bool
      """
      option = option.lower()
      if option == 'left':
          return 'left'
      elif option == 'right':
          return 'right'
      elif option in ['off', 'false', '0', 'no', 'none']:
          return False
      else:
          raise VdtValueError(
              f"Invalid value '{option}' for option 'weeknumber', must be one of "
              "'off', 'left' or 'right'")
  
  
  def monthdisplay_option(option):
      """checks if *option* is a valid value
  
      :param option: the option the user set in the config file
      :type option: str
      :returns: firstday, firstfullweek
      :rtype: str/bool
      """
      option = option.lower()
      if option == 'firstday':
          return 'firstday'
      elif option == 'firstfullweek':
          return 'firstfullweek'
      else:
          raise VdtValueError(
              f"Invalid value '{option}' for option 'monthdisplay', must be one "
              "of 'firstday' or 'firstfullweek'"
          )
  
  
  def expand_path(path):
      """expands `~` as well as variable names"""
      return expanduser(expandvars(path))
  
  
  def expand_db_path(path):
      """expands `~` as well as variable names, defaults to $XDG_DATA_HOME"""
      if path is None:
          path = join(xdg.BaseDirectory.xdg_data_home, 'khal', 'khal.db')
      return expanduser(expandvars(path))
  
  
  def is_color(color):
      """checks if color represents a valid color
  
      raises a VdtValueError if color is not valid
      """
      # check if color is
      # 1) the default empty value
      # 2) auto
      # 3) a color name from the 16 color palette
      # 4) a color index from the 256 color palette
      # 5) an HTML-style color code
      if (color in ['', 'auto'] or
              color in COLORS.keys() or
              (color.isdigit() and int(color) >= 0 and int(color) <= 255) or
              (color.startswith('#') and (len(color) in [4, 7, 9]) and
               all(c in '01234567890abcdefABCDEF' for c in color[1:]))):
          return color
      raise VdtValueError(color)
  
  
  def test_default_calendar(config):
      """test if config['default']['default_calendar'] is set to a sensible
      value
      """
      if config['default']['default_calendar'] is None:
          pass
      elif config['default']['default_calendar'] not in config['calendars']:
          logger.fatal(
              f"in section [default] {config['default']['default_calendar']} is "
              "not valid for 'default_calendar', must be one of "
              f"{config['calendars'].keys()}"
          )
          raise InvalidSettingsError()
      elif config['calendars'][config['default']['default_calendar']]['readonly']:
          logger.fatal('default_calendar may not be read_only!')
          raise InvalidSettingsError()
  
  
  def get_color_from_vdir(path):
      try:
          color = Vdir(path, '.ics').get_meta('color')
      except CollectionNotFoundError:
          color = None
      if color is None or color == '':
          logger.debug(f'Found no or empty file `color` in {path}')
          return None
      color = color.strip()
      try:
          is_color(color)
      except VdtValueError:
          logger.warning(f"Found invalid color `{color}` in {path}color")
          color = None
      return color
  
  
  def get_unique_name(path, names):
      # TODO take care of edge cases, make unique name finding less brain-dead
      try:
          name = Vdir(path, '.ics').get_meta('displayname')
      except CollectionNotFoundError:
          logger.fatal(f'The calendar at `{path}` is not a directory.')
          raise
      if name is None or name == '':
          logger.debug(f'Found no or empty file `displayname` in {path}')
          name = os.path.split(path)[-1]
      if name in names:
          while name in names:
              name = name + '1'
      return name
  
  
  def get_all_vdirs(path):
      """returns a list of paths, expanded using glob
      """
      items = glob.glob(path)
      return items
  
  
  def get_vdir_type(_):
      # TODO implement
      return 'calendar'
  
  
  def config_checks(
          config,
          _get_color_from_vdir=get_color_from_vdir,
          _get_vdir_type=get_vdir_type):
      """do some tests on the config we cannot do with configobj's validator"""
      if len(config['calendars'].keys()) < 1:
          logger.fatal('Found no calendar section in the config file')
          raise InvalidSettingsError()
      config['sqlite']['path'] = expand_db_path(config['sqlite']['path'])
      if not config['locale']['default_timezone']:
          config['locale']['default_timezone'] = is_timezone(
              config['locale']['default_timezone'])
      if not config['locale']['local_timezone']:
          config['locale']['local_timezone'] = is_timezone(
              config['locale']['local_timezone'])
  
      # expand calendars with type = discover
      vdirs_complete = list()
      vdir_colors_from_config = {}
      for calendar in list(config['calendars'].keys()):
          if not isinstance(config['calendars'][calendar], dict):
              logger.fatal('Invalid config file, probably missing calendar sections')
              raise InvalidSettingsError
          if config['calendars'][calendar]['type'] == 'discover':
              logger.debug(
                  f"discovering calendars in {config['calendars'][calendar]['path']}"
              )
              vdirs = get_all_vdirs(config['calendars'][calendar]['path'])
              vdirs_complete += vdirs
              if 'color' in config['calendars'][calendar]:
                  for vdir in vdirs:
                      vdir_colors_from_config[vdir] = config['calendars'][calendar]['color']
              config['calendars'].pop(calendar)
      for vdir in sorted(vdirs_complete):
          calendar = {'path': vdir,
                      'color': _get_color_from_vdir(vdir),
                      'type': _get_vdir_type(vdir),
                      'readonly': False,
                      'priority': 10,
                      }
  
          # get color from config if not defined in vdir
  
          if calendar['color'] is None and vdir in vdir_colors_from_config:
              logger.debug(f"using collection's color for {vdir}")
              calendar['color'] = vdir_colors_from_config[vdir]
  
          name = get_unique_name(vdir, config['calendars'].keys())
          config['calendars'][name] = calendar
  
      test_default_calendar(config)
      for calendar in config['calendars']:
          if config['calendars'][calendar]['type'] == 'birthdays':
              config['calendars'][calendar]['readonly'] = True
          if config['calendars'][calendar]['color'] == 'auto':
              config['calendars'][calendar]['color'] = \
                  _get_color_from_vdir(config['calendars'][calendar]['path'])
  