#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: application.py
#
# Copyright 2021 Vincent Schouten
#
# 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.
#

"""
Main code for creating tkinter frames.

The frames are used as a container for other widgets.

.. _Google Python Style Guide:
   http://google.github.io/styleguide/pyguide.html

"""

import logging
import threading
from time import sleep
from tkinter import NORMAL
from powermolelib import (StateManager,
                          Heartbeat,
                          write_ssh_config_file,
                          TransferAgent,
                          Tunnel,
                          ForInstructor,
                          TorInstructor,
                          PlainInstructor,
                          BootstrapAgent)
from powermolegui.powermoleguiexceptions import SetupFailed
from powermolegui.lib.helpers import SetupLink, StateVisualiser
from powermolegui.lib.animation import AnimateItem
from powermolegui.lib.logging import LOGGER_BASENAME

# This is the main prefix used for logging
logging.basicConfig(format='%(asctime)s %(name)s %(levelname)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S')
LOGGER = logging.getLogger(f'{LOGGER_BASENAME}')  # non-class objects like fn will consult this object

# Constants, distinct ports
LOCAL_PATH_SSH_CFG = '/tmp/ssh_cfg_minitor'  # path to custom config file for ssh (generated by write_ssh_config_file())
LOCAL_PORT_AGENT = 33191  # local (forwarded) used to send instructions to Agent (all modes)
LOCAL_PORT_PROXY = 8080  # local port used to forward web traffic which exits destination host (only in TOR mode)
LOCAL_PORT_HEARTBEAT = 33193  # local port used by the heartbeat mechanism to communicate with agent (all modes)
LOCAL_PORT_TRANSFER = 33194  # local port used to upload files to destination host (only in FILE mode)
LOCAL_PORT_COMMAND = 33195  # local port used  to send linux commands to agent (only in INTERACTIVE mode)
REMOTE_PORT_AGENT = 44191  # port on destination host for Agent to listen to incoming instructions (all modes)
REMOTE_PORT_PROXY = 44192  # port on destination host for Agent to receive SOCKS proxified connections
REMOTE_PORT_HEARTBEAT = 44193  # port on destination host for Agent to respond to incoming heartbeats
REMOTE_PORT_TRANSFER = 44194  # port on destination host for Agent to receive raw file data
REMOTE_PORT_COMMAND = 44195  # port on destination host for Agent to interpret Linux commands
HOST_DEPLOY_PATH = '/tmp/'  # path on last host where the Agent will be transferred to
DEBUG = False  # set True to capture and show the output of the child (SSH) - highly experimental
HEARTBEAT_INTERVAL = 10  # specify how often (in seconds) the state of the tunnel must be checked

# Constant, grouped ports
GROUP_PORTS = {"local_port_agent": LOCAL_PORT_AGENT,
               "local_port_proxy": LOCAL_PORT_PROXY,
               "local_port_heartbeat": LOCAL_PORT_HEARTBEAT,
               "local_port_transfer": LOCAL_PORT_TRANSFER,
               "local_port_command": LOCAL_PORT_COMMAND,
               "remote_port_agent": REMOTE_PORT_AGENT,
               "remote_port_proxy": REMOTE_PORT_PROXY,
               "remote_port_heartbeat": REMOTE_PORT_HEARTBEAT,
               "remote_port_transfer": REMOTE_PORT_TRANSFER,
               "remote_port_command": REMOTE_PORT_COMMAND}


def application(main_window):  # pylint: disable=too-many-locals
    """Executes the business logic.

    To be executed in a separate thread, to avoid interference
    with the widgets.
    """
    config = main_window.configuration
    client_item, host_items, connection_items, agent_item, packet_item, status_item = main_window.canvas_items
    try:
        with StateManager() as state:
            write_ssh_config_file(LOCAL_PATH_SSH_CFG, config.gateways, config.destination)
            transferagent = TransferAgent(LOCAL_PATH_SSH_CFG, config.all_host_addr)
            if config.mode == 'FOR':
                tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS,
                                config.forwarders_string)
                instructor = ForInstructor(GROUP_PORTS)
                message = f'connections on local ports {config.forwarders_ports} will be forwarded'
            elif config.mode == 'TOR':
                tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS)
                instructor = TorInstructor(GROUP_PORTS, config.destination["host_ip"])
                message = f'local port {GROUP_PORTS["local_port_proxy"]} will be listening for SOCKS encapsulated ' \
                          'web traffic'
            elif config.mode == 'PLAIN':
                tunnel = Tunnel(LOCAL_PATH_SSH_CFG, config.mode, config.all_host_addr, GROUP_PORTS)
                instructor = PlainInstructor(GROUP_PORTS)
            bootstrap_agent = BootstrapAgent(tunnel, GROUP_PORTS, HOST_DEPLOY_PATH)
            setup_link = SetupLink(state, transferagent, tunnel, bootstrap_agent, instructor,
                                   client_item, host_items, agent_item, connection_items)
            setup_link.start()
            tunnel.periodically_purge_buffer()
            with Heartbeat(GROUP_PORTS["local_port_heartbeat"], HEARTBEAT_INTERVAL) as heartbeat:
                main_window.change_state_menu_bar_entry('send', 'Send Command', NORMAL)  # enable send/command menu bar
                main_window.instructor = instructor  # provides methods for sending files and commands
                animated_packet = AnimateItem(main_window, packet_item)
                state = StateVisualiser(heartbeat, animated_packet, status_item)
                threading.Thread(target=state.start).start()
                LOGGER.info(message)
                LOGGER.info('READY')
                while not main_window.should_terminate_application:
                    sleep(1)
    except SetupFailed as msg:  # defined in "powermoleguiexceptions", can only be raised by setup_link() in helpers
        LOGGER.error(msg)
        raise SystemExit(1)
    finally:
        sleep(3)
        main_window.after(200, main_window.destroy)
