require 'singleton'
require 'date'
require 'cgi'

module Pmux
  module Gateway 
    class History
      include Singleton
    
      @@date_format = "%Y_%m_%d"
   
      def init history_file_path, logger
        @syslog_wrapper = SyslogWrapper.instance()
        @history_file_path = history_file_path
        @logger = logger
        @last_rotate = Date.today()
        @file_path = build_file_path(@last_rotate)
        @fp = nil
        @reset = false
      end
    
      def reset history_file_path
        @history_file_path = history_file_path
        @reset = true
      end

      def finish
        @fp.close() if !@fp.nil?
      end
    
      def build_file_path d
        return "#{@history_file_path}-#{d.strftime(@@date_format)}"
      end
    
      def build_id cc
        # peername, pid, mapper, start_datetimeからユニークなidを作る
        # 内部的に使うだけなので文字列をつなげただけのもの
        return "#{cc.peername}#{cc.pid}#{cc.mapper}#{cc.start_datetime.to_s}"
      end
    
      def rotate
        # 日付が変わっていればローテート処理
        if @fp.nil?
          begin
            @fp = open(@file_path, "a")
          rescue Errno::ENOENT => e
            @logger.logging("error", "not found history file (#{@file_path})")
            @logger.logging("error", "error: #{e}")
          rescue Errno::EACCES => e
            @logger.logging("error", "can not access history file (#{@file_path})")
            @logger.logging("error", "error: #{e}")
          end
        end
        new_last_rotate = Date.today()
        if new_last_rotate.day != @last_rotate.day || @reset
          if !@fp.nil?
            @fp.close()
            @fp = nil
          else
            @logger.logging("error", "can not close file, file object is nil")
          end
          begin
            @file_path = build_file_path(new_last_rotate)
            @fp = open(@file_path, "a") 
            @last_rotate = new_last_rotate
            @reset = false
          rescue Errno::ENOENT => e
            @logger.logging("error", "not found history file (#{@file_path})")
            @logger.logging("error", "error: #{e}")
          rescue Errno::EACCES => e
            @logger.logging("error", "can not access history file (#{@file_path})")
            @logger.logging("error", "error: #{e}")
          end
        end
      end
    
      def save cc
        # idを作成する
        id = build_id(cc)
        # rotateを行う
        rotate()
        # historyに追加書き込み
        # フォーマット(タブ区切り):
        #   id\tpeername\tpid\tmapper\tstart_datetime\tend_datetime\tstatus\tcommand\n
        if !cc.end_datetime.nil?
          elapsed = ((cc.end_datetime - cc.start_datetime) * 86400).to_f
        else
          elapsed = nil
        end
        if !@fp.nil?
          msg = "#{id}\t#{cc.peername}\t#{cc.user}\t#{cc.pid}\t#{cc.mapper}\t#{cc.start_datetime.to_s}\t#{cc.end_datetime.to_s}\t#{elapsed}\t#{cc.status}\t#{cc.command}\n"
          @fp.write(msg)
          @fp.flush()
          @syslog_wrapper.logging("history", "info", msg);
        else
          @logger.logging("error", "can not write file, file object is nil")
        end
      end
    
      def is_skip elems, peername, pid, mapper, start_datetime, use_regex
        if use_regex
          return true if !peername.nil? && peername != "" &&  /#{peername}/ !~ elems[1]
          return true if !pid.nil? && pid != "" && /#{pid}/ !~ elems[3]
          return true if !mapper.nil? && mapper != "" && /#{mapper}/ !~ elems[4]
        else
          return true if !peername.nil? && peername != "" &&  peername != elems[1]
          return true if !pid.nil? && pid != "" && pid != elems[3]
          return true if !mapper.nil? && mapper != "" && mapper != elems[4]
        end
        return true if !start_datetime.nil? && start_datetime != "" && start_datetime != elems[5] 
        return false
      end
    
      def load peername, pid, mapper, start_datetime, start_date, end_date, need_command = false, html_escape = false, use_regex=false
        # 指定された期間のログファイルからデータを読み込む
        # フォーマット(タブ区切り):
        #   id\tpeername\tuser\tpid\tmapper\tstart_datetime\tend_datetime\telapsed\tstatus\tcommand\n
        # ヒストリの順番はhistory_id_orderに保存し
        # ヒストリの内容はhistoryに保存する
        history_id_order = []
        history = {}
        while (end_date - start_date).to_i >= 0 do
          file_path = build_file_path(start_date)
          begin
            open(file_path) {|file|
              while line = file.gets() do
                elems = line.chomp().split("\t")
                next if is_skip(elems, peername, pid, mapper, start_datetime, use_regex)
                id = elems.shift()
                command = elems.pop() if !need_command
                elems.unshift(start_date.to_s)
                elems.each_with_index { |e, i| elems[i] = CGI.escapeHTML(e) } if html_escape
                if history.key?(id)
                  # すでにidが存在しているのでmapだけを更新
                  history[id] = elems
                else 
                  # idが存在していないのでmapを更新してリストにidを追加
                  history_id_order << id
                  history[id] = elems
                end
              end
            }
          rescue Errno::ENOENT => e
            @logger.logging("info", "not found history file (#{file_path})")
            @logger.logging("info", "error: #{e}")
          rescue Errno::EACCES => e
            @logger.logging("info", "can not access history file (#{file_path})")
            @logger.logging("info", "error: #{e}")
          end
          start_date += 1
        end
        return [ history, history_id_order.reverse! ]
      end
    end
 end
end   
