module Locomotive::Wagon

  SiteFinder = Struct.new(:repository, :request) { def find; repository.first; end; }

  class ServeCommand < Struct.new(:path, :options, :shell)

    def initialize(path, options, shell)
      super(path, options || {}, shell)
      @parent_id = nil
    end

    def self.start(path, options = {}, shell)
      new(path, options, shell).start
    end

    def self.stop(path, force = false, shell)
      new(path, nil, shell).stop(force)
    end

    def start
      # make sure a server is not running
      stop(true) if options[:force]

      # Steam is our rendering engine
      require_steam

      # in case the developer installed custom libs
      Locomotive::Wagon.require_misc_gems

      setup_signals

      show_start_message

      show_rack_middleware_stack if options[:debug]

      # if a page, a content type or any resources of the site is getting modified,
      # then the cache of Steam will be cleared.
      listen

      # let's start!
      begin
        server.start
      rescue NoMethodError # Prevent the NilClass error from Puma to be displayed
        nil
      end
    end

    def stop(force = false)
      unless File.exist?(server_pid_file)
        shell.say "No Wagon server is running.", :red
        return
      end

      pid = File.read(server_pid_file).to_i
      Process.kill('TERM', pid)

      shell.say "\nShutting down Wagon server"

      # make sure we wait enough for the server process to stop
      sleep(2) if force
    end

    private

    def require_steam
      require 'haml'
      require 'locomotive/steam'
      require 'locomotive/steam/server'
      require 'locomotive/wagon/middlewares/error_page'      

      configure_logger

      subscribe_to_notifications

      Locomotive::Steam.configure do |config|
        config.mode           = :test
        config.adapter        = { name: :filesystem, path: File.expand_path(path), env: options[:env]&.to_sym || :local }
        config.asset_path     = File.expand_path(File.join(path, 'public'))

        config.middleware.insert_before Rack::Lint, Locomotive::Wagon::Middlewares::ErrorPage

        config.services_hook = -> (services) {
          if services.request
            services.defer(:site_finder) do
              SiteFinder.new(services.repositories.site, services.request)
            end
          end
        }
      end              
    end

    def listen
      require 'puma/plugin'

      Puma::Plugins.add_background -> {
        require_relative '../tools/listen'
        require 'locomotive/steam/adapters/filesystem/simple_cache_store'

        cache = Locomotive::Steam::Adapters::Filesystem::SimpleCacheStore.new

        Locomotive::Wagon::Listen.start(path, cache, options)
      }
    end

    def server
      @server ||= build_puma_server
    end

    def build_puma_server
      require 'rackup'
      require 'rack/handler/puma'

      Rackup::Handler::Puma.run(Locomotive::Steam.to_app,
        daemon:           options[:daemonize],
        pidfile:          server_pid_file,
        redirect_stdout:  options[:daemonize] ? server_log_file : nil,
        Threads:          ENV['PUMA_THREADS'] || '1:4',
        Host:             options[:host],
        Port:             options[:port],
        Verbose:          ENV['PUMA_DEBUG_ON'] || false,
        Silent:           true
      )
    end

    def configure_logger
      # make sure the logs folder exist and get rid of that ugly error message if it doesn't
      FileUtils.mkdir_p(File.join(path, 'log'))

      Locomotive::Common.reset
      Locomotive::Common.configure do |config|
        logger = options[:daemonize] ? log_file : nil
        config.notifier = Locomotive::Common::Logger.setup(logger)
      end
    end

    def subscribe_to_notifications
      # Page not found
      ActiveSupport::Notifications.subscribe('steam.render.page_not_found') do |name, start, finish, id, payload|
        fullpath, locale, default_locale = payload[:path], payload[:locale], payload[:default_locale]

        filepath = File.join(File.expand_path(path), 'app', 'views', 'pages', fullpath + (locale != default_locale ? ".#{locale}" : '') + '.liquid')

        message = if File.exist?(filepath)
          "[Warning]".red + ' by default and unless you override the slug in the YAML header of your page, Wagon will replace underscores by dashes in your page slug. Try this instead: ' + fullpath.dasherize.light_white
        else
          "[Tip]".light_white + " add a new page in your Wagon site at this location: " + filepath.light_white
        end

        Locomotive::Common::Logger.info (' ' * 2) + message
      end
    end

    def setup_signals
      %w(INT TERM).each do |signal|
        trap(signal) do
          show_stop_message

          EM.add_timer(1) do
            server.stop
          end
        end
      end
    end

    def server_pid_file
      File.join(File.expand_path(path), 'log', 'server.pid')
    end

    def server_log_file
      File.join(File.expand_path(path), 'log', 'server.log')
    end

    def log_file
      File.expand_path(File.join(path, 'log', 'wagon.log'))
    end

    def show_rack_middleware_stack
      shell.say "[Rendering stack]".colorize(color: :light_white, background: :blue)

      Locomotive::Steam.configuration.middleware.list.each do |middleware|
        shell.say "\t" + middleware.first.first.inspect
      end

      shell.say "\n"
    end

    def show_start_message
      shell.say "Your site is served now.\nBrowse http://#{options[:host]}:#{options[:port]}\n\n", :green
    end

    def show_stop_message
      shell.say "\nShutting down Wagon server"
    end

  end

end
