require 'logger'
require 'tempfile'

require 'jrjackson'
require 'rack'

require_relative 'mjolnir'
require_relative 'metadata'
require_relative 'store'
require_relative 'stats'
require_relative 'input'
require_relative 'filter'
require_relative 'output'


module Anschel
  class Main < Mjolnir


    desc 'version', 'Show application version'
    def version
      puts VERSION
    end


    desc 'art', 'Show application art'
    def art
      puts "\n%s\n" % ART
    end


    desc 'agent', 'Run application'
    option :config, \
      type: :string,
      aliases: %w[ -c ],
      desc: 'Path to primary configuration file',
      default: '/etc/anschel.json'
    include_common_options
    def agent
      log.info \
        event: 'hello',
        version: VERSION,
        options: options.to_hash,
        num_cpus: num_cpus

      input, filter, output, store, stats, ts = \
        nil, nil, nil, nil, nil, nil, nil

      begin
        # Allow for //-style inline comments in JSON
        raw_config = File.read(options.config).gsub(/^\s*\/\/ .*/, '')

        config = JrJackson::Json.load \
          raw_config, symbolize_keys: true

        setup_log4j config[:log4j]

        store  = Store.new config[:store], log

        stats  = Stats.new config[:stats_interval], log

        filter = Filter.new config[:filter], stats, log

        output = Output.new config[:output], stats, log

        input  = Input.new \
          config[:input], config[:queue_size], stats, log, store[:input]

        stats.create 'event'
        stats.get 'event'

        ts = []

        stats_port = config[:stats_port] || 3345
        ts << stats_endpoint(stats, stats_port, log)

        ts += num_cpus.times.map do
          Thread.new do
            loop do
              event = JrJackson::Json.load \
                input.shift, symbolize_keys: true
              output.push filter.apply(event)
              stats.inc 'event'
            end
          end
        end

        ts.each { |t| t.abort_on_exception = true }

      rescue Exception => e
        log.fatal \
          event: 'exception',
          exception: e.inspect,
          class: e.class,
          message: e.message,
          backtrace: e.backtrace
        bye output, input, store, log, :error
        exit 2
      end

      log.info event: 'all-systems-clear'

      trap('SIGINT') do
        bye output, input, store, log
        exit
      end

      ts.map &:join
    end


  private
    def bye output, input, store, log, level=:info
      if input && output
        output.stop ; input.stop
        store[:input] = input.leftovers
        log.debug event: 'flush'
      end
      log.send level, event: 'goodbye', version: VERSION
    end


    def stats_endpoint stats, stats_port, log
      ok = '200'
      json = { 'Content-Type' => 'application/json' }

      app = lambda do |_|
        hash_stats = { version: VERSION }.merge stats.read
        json_stats = JSON.pretty_generate hash_stats
        [ ok, json, [ json_stats ] ]
      end

      t = Thread.new do
        Rack::Handler::WEBrick.run app, \
          Port: stats_port,
          Logger: log,
          AccessLog: []
      end

      log.info event: 'serving-metrics', port: stats_port
      return t
    end

  end
end