#!/usr/bin/env python
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program 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.
#
# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

"""
Entry point of the server. It's support daemonize the process
"""

# WARNING
# Due to buggy user machines we choose to put this as the first loading
# otherwise the egg cache is initialized in his standard location and
# if is not writetable the application crash. It's the user fault
# because one day the user as used sudo to run an egg and break his
# filesystem permissions, but it's a common mistake.
import gns3server.utils.get_resource

import os
import sys
import asyncio
import argparse
import logging
import resource


def daemonize():
    """
    Do the UNIX double-fork magic for properly detaching process
    """
    try:
        pid = os.fork()
        if pid > 0:
            # Exit first parent
            sys.exit(0)
    except OSError as e:
        print("First fork failed: %d (%s)\n" % (e.errno, e.strerror), file=sys.stderr)
        sys.exit(1)

    # Decouple from parent environment
    os.setsid()
    os.umask(0o007)

    # Do second fork
    try:
        pid = os.fork()
        if pid > 0:
            # Exit from second parent
            sys.exit(0)
    except OSError as e:
        print("Second fork failed: %d (%s)\n" % (e.errno, e.strerror), file=sys.stderr)
        sys.exit(1)

def parse_arguments(argv):
    """
    Parse command line arguments

    :param argv: Array of command line arguments
    """
    from gns3server.version import __version__
    parser = argparse.ArgumentParser(description=f"GNS3 server version {__version__}")
    parser.add_argument("-v", "--version", help="show the version", action="version", version=__version__)
    parser.add_argument("--host", help="run on the given host/IP address")
    parser.add_argument("--port", help="run on the given port", type=int)
    parser.add_argument("--ssl", action="store_true", help="run in SSL mode")
    parser.add_argument("--config", help="Configuration file")
    parser.add_argument("--certfile", help="SSL cert file")
    parser.add_argument("--certkey", help="SSL key file")
    parser.add_argument("-L", "--local", action="store_true", help="local mode (allows some insecure operations)")
    parser.add_argument(
        "-A", "--allow", action="store_true", help="allow remote connections to local console ports"
    )
    parser.add_argument("-q", "--quiet", default=False, action="store_true", help="do not show logs on stdout")
    parser.add_argument("-d", "--debug", default=False, action="store_true", help="show debug logs")
    parser.add_argument("--logfile", "--log", help="send output to logfile instead of console")
    parser.add_argument("--logmaxsize", default=10000000, help="maximum logfile size in bytes (default is 10MB)")
    parser.add_argument(
        "--logbackupcount", default=10, help="number of historical log files to keep (default is 10)"
    )
    parser.add_argument(
        "--logcompression", default=False, action="store_true", help="compress inactive (historical) logs"
    )
    parser.add_argument("--daemon", action="store_true", help="start as a daemon")
    parser.add_argument("--pid", help="store process pid")
    parser.add_argument("--profile", help="Settings profile (blank will use default settings files)")

    args = parser.parse_args(argv)
    return parser, args


log = logging.getLogger(__name__)


def _raise_open_files_limit(target=65535):
    """
    Raise RLIMIT_NOFILE at startup so large topologies don't hit EMFILE.
    Every started node holds ~3 file descriptors in the server's table
    (pidfd + stdout/stderr pipes per child process), so a few hundred nodes
    exhaust the default 1024 limit. Best-effort: the hard limit caps what we
    can request; failures are logged but never fatal. Runs before daemonize()
    so the daemon inherits the raised limit.
    """
    try:
        soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
        if soft >= target:
            return
        new_soft = min(target, hard)
        resource.setrlimit(resource.RLIMIT_NOFILE, (new_soft, hard))
        if new_soft < target:
            log.warning(
                f"Open-files limit raised to {new_soft} (hard limit), below the requested {target}"
            )
        else:
            log.info(f"Open-files limit raised from {soft} to {new_soft}")
    except (OSError, ValueError) as e:
        log.warning(f"Could not raise the open-files limit: {e}")


def main():
    """
    Entry point for GNS3 server
    """

    if sys.platform.startswith("win"):
        raise SystemExit("Windows is not a supported platform to run the GNS3 server")
    _raise_open_files_limit()
    if "--daemon" in sys.argv:
        daemonize()

    try:
        parser, args = parse_arguments(sys.argv[1:])
        from gns3server.server import Server
        asyncio.run(Server().run(parser, args))
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()
