# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements.  See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.  The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License.  You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations
# under the License.

require_relative 'body_proxy'

# This module is based on Rack::CommonLogger[1]
# Copyright (C) 2007, 2008, 2009, 2010 Christian Neukirchen <purl.org/net/chneukirchen>
#
# [1] https://github.com/rack/rack/blob/master/lib/rack/commonlogger.rb

module Rack
  # Rack::CommonLogger forwards every request to an +app+ given, and
  # logs a line in the Apache common log format to the +logger+, or
  # rack.errors by default.
  class DeltacloudLogger

    class << self
      def log_path(path=nil)
        @log_file ||= path
      end

      def verbose?
        @verbose
      end

      def verbose(v=nil)
        @verbose ||= v
      end

      def setup(path, be_verbose=false)
        verbose(be_verbose)
        return self if path.nil?
        dir = ::File.dirname(path)
        if ::File.exists?(dir) and ::File.writable?(dir)
          log_path(path)
        else
          warn "Warning: The log directory (#{dir}) is not writeable."
        end
        self
      end

      def logger
        @logger ||= ::Logger.new(log_path || $stdout)
      end

      [:error, :warn, :info].each do |method_name|
        define_method method_name do |msg, &block|
          logger.send(method_name, msg, &block)
        end
      end
    end

    # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
    # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
    #             %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
    FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}

    VERBOSE_FORMAT = %{%s - %s [%s] "%s %s%s%s %s" %s %s %d %s %0.4f\n}

    # If Deltacloud is started with the -L/--log option then
    # we set logger to this file, otherwise use the default
    # Sinatra logger facility
    #
    def initialize(app, logger=nil)
      @app = app
      if self.class.log_path
        @logger = self.class.logger
      else
        @logger = logger
      end
    end

    def call(env)
      began_at = Time.now
      status, header, body = @app.call(env)
      header = Utils::HeaderHash.new(header)
      body = BodyProxy.new(body) do
        self.class.verbose? ? verbose_log(env, status, header, began_at) : log(env, status, header, began_at)
      end
      [status, header, body]
    end

    private

    def verbose_log(env, status, header, began_at)
      now = Time.now
      length = extract_content_length(header)
      params = env['rack.request.form_hash'].nil? ? '' : ' '+env['rack.request.form_hash'].to_json

      logger = @logger || env['rack.errors']
      logger_method = logger.respond_to?(:info) ? :info : :puts
      logger.send logger_method, VERBOSE_FORMAT % [
        env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
        env["REMOTE_USER"] || "-",
        now.strftime("%d/%b/%Y %H:%M:%S"),
        env["REQUEST_METHOD"],
        env["PATH_INFO"],
        env["QUERY_STRING"].empty? ? '' : "?"+env["QUERY_STRING"],
        params,
        env["HTTP_VERSION"],
        env['HTTP_X_DELTACLOUD_DRIVER'] || ENV['API_DRIVER'] || 'mock',
        env['HTTP_X_DELTACLOUD_PROVIDER'] || ENV['API_PROVIDER'] || '-',
        status.to_s[0..3],
        length,
        now - began_at ]
    end

    def log(env, status, header, began_at)
      now = Time.now
      length = extract_content_length(header)

      logger = @logger || env['rack.errors']
      logger_method = logger.respond_to?(:info) ? :info : :puts
      logger.send logger_method, FORMAT % [
        env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
        env["REMOTE_USER"] || "-",
        now.strftime("%d/%b/%Y %H:%M:%S"),
        env["REQUEST_METHOD"],
        env["PATH_INFO"],
        env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
        env["HTTP_VERSION"],
        status.to_s[0..3],
        length,
        now - began_at ]
    end

    def extract_content_length(headers)
      value = headers['Content-Length'] or return '-'
      value.to_s == '0' ? '-' : value
    end
  end
end

