#!/usr/bin/python

#   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

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()

