import re
import sys


class Flags:

  def __init__(self, *args, **kwargs):
    from .config import Config
    self._config = Config(*args, **kwargs)

  def parse(self, argv=None, known_only=False, help_exists=None):
    if help_exists is None:
      help_exists = not known_only
    if argv is None:
      argv = sys.argv[1:]
    if '--help' in argv:
      print('\nHelp:')
      lines = str(self._config).split('\n')[2:]
      print('\n'.join('--' + re.sub(r'[:,\[\]]', '', x) for x in lines))
      help_exists and sys.exit()
    parsed = {}
    remaining = []
    key = None
    vals = None
    for arg in argv:
      if arg.startswith('--'):
        if key:
          self._submit_entry(key, vals, parsed, remaining)
        if '=' in arg:
          key, val = arg.split('=', 1)
          vals = [val]
        else:
          key, vals = arg, []
      else:
        if key:
          vals.append(arg)
        else:
          remaining.append(arg)
    self._submit_entry(key, vals, parsed, remaining)
    parsed = self._config.update(parsed)
    if known_only:
      return parsed, remaining
    else:
      for flag in remaining:
        if flag.startswith('--'):
          raise ValueError(f"Flag '{flag}' did not match any config keys.")
      assert not remaining, remaining
      return parsed

  def _submit_entry(self, key, vals, parsed, remaining):
    if not key and not vals:
      return
    if not key:
      vals = ', '.join(f"'{x}'" for x in vals)
      raise ValueError(f"Values {vals} were not preceeded by any flag.")
    name = key[len('--'):]
    if '=' in name:
      remaining.extend([key] + vals)
      return
    if self._config.IS_PATTERN.match(name):
      pattern = re.compile(name)
      keys = {k for k in self._config.flat if pattern.match(k)}
    elif name in self._config:
      keys = [name]
    else:
      keys = []
    if not keys:
      remaining.extend([key] + vals)
      return
    if not vals:
      raise ValueError(f"Flag '{key}' was not followed by any values.")
    for key in keys:
      parsed[key] = self._parse_flag_value(self._config[key], vals, key)

  def _parse_flag_value(self, default, value, key):
    value = value if isinstance(value, (tuple, list)) else (value,)
    if isinstance(default, (tuple, list)):
      if len(value) == 1 and ',' in value[0]:
        value = value[0].split(',')
      return tuple(self._parse_flag_value(default[0], [x], key) for x in value)
    assert len(value) == 1, value
    value = str(value[0])
    if default is None:
      return value
    if isinstance(default, bool):
      try:
        return bool(['False', 'True'].index(value))
      except ValueError:
        message = f"Expected bool but got '{value}' for key '{key}'."
        raise TypeError(message)
    if isinstance(default, int):
      value = float(value)  # Allow scientific notation for integers.
      if float(int(value)) != value:
        message = f"Expected int but got float '{value}' for key '{key}'."
        raise TypeError(message)
      return int(value)
    return type(default)(value)
