require 'logger'
require 'rbczmq'

module Zlogger
  class Client < ::Logger
    attr :options

    # Create a new logger object. This is the client logging object that sends log messages to the remote log
    # collection daemon. The options hash accepts the following options:
    #
    # :context => An existing ZMQ::Context object. Defaults to creating a new one.
    # :address => The TCP address to connect to.
    # :port    => The TCP port to connect to.
    # :name    => The name to use as a prefix for log messages, defaults to the process name and pid, eg "rails:1234"
    def initialize(options={})
      @options = options
      super(nil)
      @logdev = LogDevice.new(self)
      @logdev.run_socket_thread

      @formatter = proc do |severity, time, progname, msg|
        if msg.is_a?(Exception)
          "#{severity}: #{msg.message} (#{msg.class})\n" + (msg.backtrace || []).join("\n")
        else
          "#{severity}: #{msg}"
        end
      end
    end

    def context
      @context ||= (ZMQ.context || ZMQ::Context.new)
    end

    def queue
      @queue ||= Queue.new
    end

    def connect_address
      options[:address] || "127.0.0.1"
    end

    def port
      options[:port] || Zlogger::DEFAULT_PORT
    end

    def name
      options[:name] || "#{File.basename($0)}:#{Process.pid}"
    end

    def log_device
      @logdev
    end

    def close
      @logdev.close
    end

    class LogDevice
      attr :client

      def initialize(client)
        @client = client
      end

      def write(message)
        client.queue << message
      end

      def close()
        client.queue << self
      end

      # it is not threadsafe to access ZMQ sockets, so we only write to the logging socket from a single thread.
      def run_socket_thread
        @thread ||= Thread.new { run_socket_loop }
      end

      def run_socket_loop
        begin
          socket = client.context.socket :PUB
          socket.connect("tcp://#{client.connect_address}:#{client.port}")
          loop do
            object = client.queue.pop
            break if object == self
            message = ZMQ::Message.new
            message.addstr(client.name)
            message.addstr(object.to_s)
            socket.send_message(message)
          end
          socket.close
        rescue StandardError => e
          puts "Logging socket thread error: #{e}"
        end
      end
    end
  end
end
