#!/usr/bin/env python3
# pylint: disable=invalid-name

import argparse
import logging
import logging.handlers
import sys

import i3ipc

from i3wsgroups import i3_workspace_groups

_LIST_WORKSPACES_FIELDS = [
    'group', 'local_name', 'global_name', 'local_number', 'global_number'
]
_LIST_WORKSPACES_FIELDS_HELP = (
    'Comma separated list of fields to output. Options: {}').format(
        ', '.join(_LIST_WORKSPACES_FIELDS))

_LOG_FMT = '%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s'

def _create_args_parser() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description='Control i3 workspace groups.')
    parser.add_argument(
        '--dry-run',
        action='store_true',
        default=False,
        help='If true, will not actually do any changes to i3 workspaces.')
    parser.add_argument(
        '--log-level',
        choices=('debug', 'info', 'warning', 'error', 'critical'),
        default='warning',
        help='Logging level for stderr and syslog.')
    # The argparse argument group of the workspace group arguments.
    group_arg_group = parser.add_mutually_exclusive_group()
    group_arg_group.add_argument(
        '--group-active',
        action='store_true',
        default=None,
        help=
        'Use the active group for any commands that implicitly assume a group, '
        'such as workspace-next.')
    group_arg_group.add_argument(
        '--group-focused',
        action='store_true',
        default=None,
        help='Use the focused group for any commands that implicitly assume a '
        'group, such as workspace-next.')
    group_arg_group.add_argument('--group-name')
    subparsers = parser.add_subparsers(dest='command')
    subparsers.required = True
    subparsers.add_parser(
        'list-groups', help='List the groups of the current workspaces.')
    list_workspaces_parser = subparsers.add_parser(
        'list-workspaces', help='List workspaces and their group.')
    list_workspaces_parser.add_argument(
        '--fields',
        default=','.join(_LIST_WORKSPACES_FIELDS),
        help=_LIST_WORKSPACES_FIELDS_HELP)
    list_workspaces_parser.add_argument(
        '--focused-only',
        action='store_true',
        help='List only the focused workspace in the given group context.')
    subparsers.add_parser(
        'workspace-number',
        help='Focus on the workspace with the provided number in the focused '
        'group, similar to i3\'s "workspace number" command').add_argument(
            'workspace_relative_number', type=int)
    subparsers.add_parser(
        'move-to-number',
        help='Move the focused container to the workspace with the provided '
        'number in the focused group, similar to i3\'s "move container to '
        'workspace" command').add_argument(
            'workspace_relative_number', type=int)
    subparsers.add_parser(
        'workspace-next',
        help='Focus on the next workspace in the focused group, similar to '
        'i3\'s "workspace next" command')
    subparsers.add_parser(
        'workspace-prev',
        help='Focus on the prev workspace in the focused group, similar to '
        'i3\'s "workspace prev" command')
    subparsers.add_parser(
        'move-to-next',
        help='Move the focused container to the prev workspace in the focused '
        'group, similar to i3\'s "move container to workspace next" command')
    subparsers.add_parser(
        'move-to-prev',
        help='Move the focused container to the prev workspace in the focused '
        'group, similar to i3\'s "move container to workspace prev" command')
    subparsers.add_parser(
        'rename-workspace',
        help='Similar to i3\'s "renamed workspace" command, but only changes '
        'the name of the workspace within the group, while preserving its '
        'group assignment').add_argument('new_name')
    switch_active_group_parser = subparsers.add_parser(
        'switch-active-group',
        help='Switches the active group to the one provided.')
    switch_active_group_parser.add_argument('group')
    switch_active_group_parser.add_argument(
        '--focused-monitor-only', action='store_true')
    subparsers.add_parser(
        'assign-workspace-to-group',
        help='Assigns the focused workspace to the provided group.'
    ).add_argument('group')
    return parser


def _create_context_group_finder(args, i3_connection):
    if args.group_name:
        return i3_workspace_groups.NamedGroupContext(args['group_name'])
    if args.group_active:
        return i3_workspace_groups.ActiveGroupContext(i3_connection)
    if args.group_focused:
        return i3_workspace_groups.FocusedGroupContext()
    return None


def _init_logger(logger: logging.Logger):
    syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
    stdout_handler = logging.StreamHandler()
    stdout_formatter = logging.Formatter(_LOG_FMT)
    stdout_handler.setFormatter(stdout_formatter)
    syslog_formatter = logging.Formatter('i3-workspace-groups: ' + _LOG_FMT)
    syslog_formatter.ident = 'i3-workspace-groups'
    syslog_handler.setFormatter(syslog_formatter)
    logger.addHandler(syslog_handler)
    logger.addHandler(stdout_handler)

def _init_logger(logger):
    syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
    stdout_handler = logging.StreamHandler()
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    syslog_handler.setFormatter(formatter)
    stdout_handler.setFormatter(formatter)
    logger.addHandler(syslog_handler)
    logger.addHandler(stdout_handler)
    return logger


def _get_workspace_field(workspace, field):
    if field == 'global_name':
        return workspace.name
    parsed_name = i3_workspace_groups.parse_workspace_name(workspace.name)
    value = parsed_name[field]
    if value is None:
        return ''
    return value


def _print_workspaces(controller, args):
    fields = args.fields.split(',')
    for field in fields:
        if field not in _LIST_WORKSPACES_FIELDS:
            sys.exit('Invalid field: "{}". Valid fields: {}'.format(
                field, _LIST_WORKSPACES_FIELDS))
    table = []
    for workspace in controller.list_workspaces(args.focused_only):
        row = []
        for field in fields:
            row.append(_get_workspace_field(workspace, field))
        table.append(row)
    print('\n'.join('\t'.join(str(e) for e in row) for row in table))


def main():
    args = _create_args_parser().parse_args()
    logger = i3_workspace_groups.logger
    _init_logger(logger)
    logger.setLevel(getattr(logging, args.log_level.upper(), None))
    i3_connection = i3ipc.Connection()
    group_context = _create_context_group_finder(args, i3_connection)
    controller = i3_workspace_groups.WorkspaceGroupsController(
        i3ipc.Connection(), group_context, args.dry_run)
    try:
        if args.command == 'list-groups':
            print('\n'.join(controller.list_groups()))
        elif args.command == 'list-workspaces':
            _print_workspaces(controller, args)
        elif args.command == 'workspace-number':
            controller.focus_workspace_number(args.workspace_relative_number)
        elif args.command == 'move-to-number':
            controller.move_to_workspace_number(args.workspace_relative_number)
        elif args.command == 'workspace-next':
            controller.focus_workspace_relative(+1)
        elif args.command == 'workspace-prev':
            controller.focus_workspace_relative(-1)
        elif args.command == 'move-to-next':
            controller.move_workspace_relative(+1)
        elif args.command == 'move-to-prev':
            controller.move_workspace_relative(-1)
        elif args.command == 'rename-workspace':
            controller.rename_workspace(args.new_name)
        elif args.command == 'switch-active-group':
            controller.switch_active_group(args.group,
                                           args.focused_monitor_only)
        elif args.command == 'assign-workspace-to-group':
            controller.assign_workspace_to_group(args.group)
    except i3_workspace_groups.WorkspaceGroupsError as ex:
        sys.exit(ex)


if __name__ == '__main__':
    main()
