#!/usr/bin/env python3

#   Copyright (C) 2018      Simone Margaritelli
#                 2018      MiWCryptAnalytics
#                 2019-2023 Gustavo Iñiguez Goia
#
#   This file is part of OpenSnitch.
#
#   OpenSnitch is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   OpenSnitch is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with OpenSnitch.  If not, see <http://www.gnu.org/licenses/>.

from PyQt5 import QtWidgets, QtGui, QtCore

import sys
import os
import time
import signal
import argparse
import logging
from threading import Timer

from concurrent import futures

import grpc

dist_path = '/usr/lib/python3/dist-packages/'
if dist_path not in sys.path:
    sys.path.append(dist_path)

from opensnitch.service import UIService
from opensnitch.config import Config
from opensnitch.utils import Themes, Utils, Versions
import opensnitch.ui_pb2
from opensnitch.ui_pb2_grpc import add_UIServicer_to_server

def on_exit():
    server.stop(0)
    app.quit()
    sys.exit(0)

def supported_qt_version(major, medium, minor):
    q = QtCore.QT_VERSION_STR.split(".")
    return int(q[0]) >= major and int(q[1]) >= medium and int(q[2]) >= minor

if __name__ == '__main__':
    gui_version, grpcversion, protoversion = Versions.get()
    print("\t ~ OpenSnitch GUI -", gui_version, "~")
    print("\tprotobuf:", protoversion, "-", "grpc:", grpcversion)
    print("-" * 50, "\n")

    parser = argparse.ArgumentParser(description='OpenSnitch UI service.', formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument("--socket", dest="socket", help='''
Path of the unix socket for the gRPC service (https://github.com/grpc/grpc/blob/master/doc/naming.md).
Default: unix:///tmp/osui.sock

Examples:
    - Listening on Unix socket: opensnitch-ui --socket unix:///tmp/osui.sock
        * Use unix:///run/1000/YOUR_USER/opensnitch/osui.sock for better privacy.
    - Listening on port 50051, all interfaces: opensnitch-ui --socket "[::]:50051"
                        ''', metavar="FILE")
    parser.add_argument("--max-clients", dest="serverWorkers", default=10, help="Max number of allowed clients (incoming connections).")
    parser.add_argument("--debug", dest="debug", action="store_true", help="Enable debug logs")
    parser.add_argument("--debug-grpc", dest="debug_grpc", action="store_true", help="Enable gRPC debug logs")

    args = parser.parse_args()

    if args.debug:
        import faulthandler
        faulthandler.enable()

    logging.getLogger().disabled = not args.debug

    if args.debug and args.debug_grpc:
        os.environ["GRPC_TRACE"] = "all"
        os.environ["GRPC_VERBOSITY"] = "debug"

    os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1"
    if supported_qt_version(5,6,0):
        try:
            # NOTE: maybe we also need Qt::AA_UseHighDpiPixmaps
            QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
        except Exception:
            pass

    app = QtWidgets.QApplication(sys.argv)
    if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
        app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
    thm = Themes.instance()
    thm.load_theme(app)

    Utils.create_socket_dirs()
    cfg = Config.get()
    if args.socket == None:
        # default
        args.socket = "unix:///tmp/osui.sock"

        addr = cfg.getSettings(Config.DEFAULT_SERVER_ADDR)
        if addr != None and addr != "":
            if addr.startswith("unix://"):
                if not os.path.exists(os.path.dirname(addr[7:])):
                    print("WARNING: unix socket path does not exist, using unix:///tmp/osui.sock, ", addr)
                else:
                    args.socket = addr
            else:
                args.socket = addr

    print("Using server address:", args.socket)

    maxmsglencfg = cfg.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH)
    if maxmsglencfg == '4MiB':
        maxmsglen = 4194304
    elif maxmsglencfg == '8MiB':
        maxmsglen = 8388608
    elif maxmsglencfg == '16MiB':
        maxmsglen = 16777216
    else:
        maxmsglen = 4194304

    print("gRPC Max Message Length:", maxmsglencfg)
    print("                  Bytes:", maxmsglen)

    service = UIService(app, on_exit)
    # @doc: https://grpc.github.io/grpc/python/grpc.html#server-object
    server = grpc.server(futures.ThreadPoolExecutor(),
                         options=(
                             # https://github.com/grpc/grpc/blob/master/doc/keepalive.md
                             # https://grpc.github.io/grpc/core/group__grpc__arg__keys.html
                             # send keepalive ping every 5 second, default is 2 hours)
                             ('grpc.keepalive_time_ms', 5000),
                             # after 5s of inactivity, wait 20s and close the connection if
                             # there's no response.
                             ('grpc.keepalive_timeout_ms', 20000),
                             ('grpc.keepalive_permit_without_calls', True),
                             ('grpc.max_send_message_length', maxmsglen),
                             ('grpc.max_receive_message_length', maxmsglen),
                         ))

    add_UIServicer_to_server(service, server)

    if args.socket.startswith("unix://"):
        socket = args.socket[7:]
        socket = os.path.abspath(socket)
        server.add_insecure_port("unix:%s" % socket)
    else:
        server.add_insecure_port(args.socket)

    # https://stackoverflow.com/questions/5160577/ctrl-c-doesnt-work-with-pyqt
    signal.signal(signal.SIGINT, signal.SIG_DFL)

    try:
        # print "OpenSnitch UI service running on %s ..." % socket
        server.start()
        app.exec_()
    except KeyboardInterrupt:
        on_exit()

