require 'ostruct'
require 'optparse'

module NginxTail
  class Application

    include NginxTail::Options

    # default application options...
    DEFAULT_OPTIONS = {
      :interrupted => false,
      :running => true,
      :pattern => :nginx,
      :exit => 0,
    }

    # parsed application options...
    @options = nil

    def respond_to?(symbol, include_private = false)
      @options.respond_to?(symbol) || super
    end

    def method_missing(methodId)
      respond_to?(methodId) ? @options.send(methodId.to_sym) : super
    end

    def initialize(argv = [])
      @options = parse_options(argv, DEFAULT_OPTIONS)
    end

    def run!

      LogLine.set_pattern(@options.pattern)

      ['TERM', 'INT'].each do |signal|
        Signal.trap(signal) do
          @options.running = false ; @options.interrupted = true
          $stdin.close if ARGF.file == $stdin # ie. reading from STDIN
        end
      end

      files_read = lines_read = lines_processed = lines_ignored = parsable_lines = unparsable_lines = 0

      current_filename = nil ; current_line_number = 0 ; file_count = ARGV.count

      while @options.running and ARGF.gets
        if ARGF.file.lineno == 1
          current_filename = ARGF.filename ; current_line_number = 0
          files_read += 1
          if @options.verbose
            $stderr.puts "[INFO] now processing file #{ARGF.filename}"
          end
        end
        raw_line = $_.chomp ; lines_read += 1 ; current_line_number += 1
        unless @options.dry_run
          if !@options.line_number or @options.line_number == ARGF.lineno
            begin
              log_line = NginxTail::LogLine.new(raw_line, current_filename, current_line_number)
              if log_line.parsable
                parsable_lines += 1
                unless @options.parse_only
                  if !@options.filter || log_line.instance_eval(@options.filter)
                    lines_processed += 1
                    if @options.code
                      log_line.instance_eval(@options.code)
                    elsif @options.raw
                      $stdout.puts raw_line
                      sleep @options.sleep if @options.sleep
                    else
                      puts log_line.to_s(:color => true)
                    end
                  else
                    lines_ignored += 1
                    if @options.verbose
                      $stderr.puts "[WARNING] ignoring line ##{lines_read}"
                    end
                  end
                end
              else
                unparsable_lines += 1
                if @options.verbose
                  $stderr.puts "[ERROR] cannot parse '#{raw_line}'"
                end
              end
            rescue
              $stderr.puts "[ERROR] processing line #{ARGF.file.lineno} of file #{ARGF.filename} resulted in #{$!.message}"
              $stderr.puts "[ERROR] " + raw_line
              @options.exit = -1
              @options.running = false
              raise $! # TODO if the "re-raise exceptions" option has been set...
            end
          end
        end
        if @options.progress
          progress_line = [
            " Processing file ".inverse + (" %d/%d" % [files_read, file_count]),
            " Current filename ".inverse + " " + current_filename.to_s,
            " Line number ".inverse + " " + current_line_number.to_s,
            " Lines processed ".inverse + " " + lines_read.to_s
          ].join(" \342\200\242 ")
          max_length = [max_length || 0, progress_line.size].max
          $stderr.print progress_line
          $stderr.print " " * (max_length - progress_line.size)
          $stderr.print "\r"
        end
      end

      $stderr.puts if @options.progress

      if @options.verbose
        $stderr.puts if @options.interrupted
        $stderr.print "[INFO] read #{lines_read} line(s) in #{files_read} file(s)"
        $stderr.print " (interrupted)" if @options.interrupted ; $stderr.puts
        $stderr.puts "[INFO] #{parsable_lines} parsable lines, #{unparsable_lines} unparsable lines"
        $stderr.puts "[INFO] processed #{lines_processed} lines, ignored #{lines_ignored} lines"
      end

      return @options.exit

    end

  end
end