#### Aya - A Gemini server
#### Copyright (C) 2021 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify
#### it under the terms of the GNU Affero 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 Affero General Public License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program.  If not, see <https://www.gnu.org/licenses/>.
require "mime"
require "yaml"
require "libremiliacr/remiargparser"
require "libremiliacr/remiconsole"
require "libremiliacr/remilog"

require "./utils"
require "./config"
require "./server"

module Aya
  VERSION = "0.1.2"
  DEFAULT_CONFIG_FILE = "aya-config.yaml"
  GEMINI_MIME_TYPE = "text/gemini; charset=utf-8"

  @@args : RemiArgParser::ArgParser = RemiArgParser::ArgParser.new(PROGRAM_NAME, VERSION)
  class_getter args

  @@config : Config? = nil
  class_getter! config

  @@connLogger : RemiLog::Logger? = nil
  class_getter connLogger

  private def self.initCmdLine
    @@args.addString("config", 'c', help: "The configuration file to load")
    @@args.addFlag("init-only", 'I', help: "Initialize, but do not start, Aya.")
    @@args.addFlag("verbose", 'v', "Logging", "Enable verbose messages")
    @@args.addFlag("debug", 'g', "Logging", "Enable debug messages")

    begin
      @@args.parse
    rescue err : RemiArgParser::ArgumentError
      RemiLog.log.fatal("#{err}")
    end
  end

  private def self.initLogging
    if @@args["verbose"].called
      RemiLog.log.verbosityLevel = 255u8
    end

    if @@args["debug"].called
      RemiLog.log.debugLevel = 255u8
      RemiLog.log.verbosityLevel = 255u8
    end

    RemiLog.log.timestamp = "%c"
    RemiLog.log.defaultHeader = "Aya"
  end

  private def self.initConfig
    RemiLog.log.vlog("Init configuration file")
    if @@args["config"].called
      @@config = Config.fromFile(@@args["config"].str)
    elsif File.exists?(DEFAULT_CONFIG_FILE)
      @@config = Config.fromFile(DEFAULT_CONFIG_FILE)
    else
      RemiLog.log.fatal("No configuration file found")
    end

    Aya.config.validate
    Aya.config.report
  end

  private def self.initMime
    RemiLog.log.vlog("Init MIME database")
    unless Aya.config.mimeFile.empty?
      RemiLog.log.vlog("Loading MIME type database: #{Aya.config.mimeFile}")
      File.open(Aya.config.mimeFile) do |file|
        MIME.load_mime_database(file)
      end
    end

    MIME.register(".gmi", GEMINI_MIME_TYPE)
    MIME.register(".gemini", GEMINI_MIME_TYPE)
    RemiLog.log.dlog("Registered MIME types for Gemini files")
  end

  private def self.initLogFiles
    unless Aya.config.logFile.empty?
      RemiLog.log.vlog("Init log file: #{Aya.config.logFile}")
      RemiLog.log.otherStreams << File.open(Aya.config.logFile, "w")
    end

    unless Aya.config.connLogFile.empty?
      @@connLogger = RemiLog::Logger.new
      connLogger.not_nil!.timestamp = "%c"
    end
  end

  Signal::INT.trap do
    RemiLog.log.log("Ctrl-C detected, shutting down...")
    RemiLog.log.otherStreams.each &.flush
    RemiLog.log.otherStreams.each &.close
    exit
  end

  Signal::TERM.trap do
    RemiLog.log.log("TERM signal detected, shutting down...")
    RemiLog.log.otherStreams.each &.flush
    RemiLog.log.otherStreams.each &.close
    exit
  end

  # Possibly excessive?
  at_exit do
    RemiLog.log.otherStreams.each &.flush
    RemiLog.log.otherStreams.each &.close
  end

  # Setup and parse command line arguments
  initCmdLine

  # Initialize basic logging
  initLogging

  # Load configuration
  initConfig

  # Startup log file handling
  initLogFiles

  # Initialize MIME types
  initMime

  # Exit if --init-only was used
  exit 0 if @@args["init-only"].called

  # Change UID/GID as-needed.  The GID must be set first in case the new UID
  # does not have permissions to change the GID.
  if !Aya.config.gid.nil?
    RemiLog.log.log("Changing GID to #{Aya.config.gid}")
    unless (err = LibC.setregid(Aya.config.gid.not_nil!)) == 0
      RemiLog.log.fatal("Could not change GID, error #{Errno.value}")
    end
  elsif LibC.getgid == 0
    RemiLog.log.warn("You are running as the root group!")
  end

  if !Aya.config.uid.nil?
    RemiLog.log.log("Changing UID to #{Aya.config.uid}")
    unless (err = LibC.setreuid(Aya.config.uid.not_nil!)) == 0
      RemiLog.log.fatal("Could not change UID, error #{Errno.value}")
    end
  elsif LibC.getuid == 0
    RemiLog.log.warn("You are running as root!")
  end

  if LibC.geteuid() == 0
    RemiLog.log.warn("Real UID is still root!")
  end

  if LibC.getegid() == 0
    RemiLog.log.warn("Real GID is still root!")
  end

  # Start the server
  Server.new.run
end
