require 'logger'
require 'singleton'

require "tlogger/version"

module Tlogger
  class Error < StandardError; end
  # Your code goes here...

  # 
  # Singleton object to facilitate tag management
  #
  class TloggerConf
    include Singleton
    attr_reader :active_tags, :scoped_tag, :blacklisted_tags, :show_source
    #GLOBAL_TAG = :global
    GLOBAL_TAG = ""
    INT_TAG = :tlogger
    
    def initialize
      @active_tags = []
      # tag added to black listed will not be printed out even the tag is added later in the code path
      @blacklisted_tags = []
      @disable_all_tags = false
      @auto_tag = false
      # todo
      # allow to redirect certain tag to specific output. tag: [<config>]
      @output_map = {}
      # if output_map is there, then there should be pre created log file with specific output defined in the map. [<config>] => log_instance
      @output_log = {}
      # show where is the log being printed out. Like auto_tag output
      @show_source = false
      # end todo
    end

    def show_source
      @show_source = true
    end
    alias_method :auto_tag_on, :show_source

    def hide_source
      @show_source = false
    end
    alias_method :auto_tag_off, :hide_source

    def is_show_source?
      @show_source
    end
    alias_method :is_auto_tag_on?, :is_show_source?

    #def auto_tag_on
    #  @auto_tag = true
    #end

    #def auto_tag_off
    #  @auto_tag = false
    #end

    #def is_auto_tag_on?
    #  @auto_tag
    #end

    def activate_tag(tag)
      @active_tags << tag
    end

    def deactivate_tag(tag)
      @active_tags.delete(tag)
    end

    def blacklist_tag(tag)
      if tag.is_a?(Array)
        @blacklisted_tags += tag
      else
        @blacklisted_tags << tag
      end
    end

    def all_tags_off
      @disable_all_tags = true
    end

    def all_tags_on
      @disable_all_tags = false
    end

    def whitelist_tag(tag)
      if tag.is_a?(Array)
        tag.each do |t|
          @blacklisted_tags.delete(t)
        end
      else
        @blacklisted_tags.delete(tag)
      end
    end

    def whitelist_all_tags
      @disabled_tags.clear
    end
   
    def is_tag_active?(tag)
      if @disable_all_tags
        false
      else
        @active_tags.include?(tag) #and not @disabled_tags.include?(tag)
      end
    end

    def remove_all_active_tags
      @active_tags.clear
    end

    def set_scoped_tag(tag)
      @scoped_tag = tag
    end

    def clear_scoped_tag
      @scoped_tag = nil
    end

    def has_scoped_tag?
      if @disable_all_tags
        false
      else
        not @scoped_tag.nil? and not @scoped_tag.empty?
      end
    end

    def is_scoped_tag_active?
      #@active_tags.include?(@scoped_tag)
      not @blacklisted_tags.include?(@scoped_tag)
    end

    def self.method_missing(mtd,*args,&block)
      if TloggerConf.instance.respond_to?(mtd)
        TloggerConf.instance.send(mtd,*args,&block)
      else
        super
      end
    end
  end
  # 
  # end TloggerConf singleton
  #

  # 
  # add object like methods to make module a class
  #
  class << self
    attr_accessor :tag
    include Tlogger
    
    PROXY_MTD = [:debug, :info, :error, :warn]
    def new(*args)
      if args.length == 0
        args << STDOUT
      end
      @tlogger = Logger.new(*args)
      @tag = TloggerConf::GLOBAL_TAG
      # disable by default
      # If there is issue then enable it back in application level
      self
    end

    def tag=(val)
      @tag = val
      TloggerConf.activate_tag(@tag)
      self
    end

    def tdebug(tag,*args)
      with_tag(tag) do
        self.debug(*args)
      end
      self
    end

    def terror(tag, *args)
      with_tag(tag) do
        self.error(*args)
      end
      self
    end

    def tinfo(tag, *args)
      with_tag(tag) do
        self.info(*args)
      end
      self
    end

    def twarn(tag, *args)
      with_tag(tag) do
        self.warn(*args)
      end
      self
    end

    def intDebug(msg)
      #puts TloggerConf.active_tags
      if TloggerConf.instance.is_tag_active?(TloggerConf::INT_TAG)
        msg2 = "[#{TloggerConf::INT_TAG}] #{msg}"
        #puts "intDebug"
        @tlogger.debug(msg2)
      end
    end

    def method_missing(mtd,*args,&block)
      #@tlogger.debug "[tlogger] method_missing: Method #{mtd}"
      intDebug("method_missing: #{mtd}")
      if @tlogger.respond_to?(mtd)

        if PROXY_MTD.include?(mtd)
          
          if @tag.nil? or @tag.empty?
            # no tag. Output like normal log
            @tlogger.send(mtd, *args, &block)
            
          else

            if TloggerConf.has_scoped_tag?
              if TloggerConf.is_scoped_tag_active?
                intDebug("Scoped tag detected")
                tag = []
                tag << TloggerConf.scoped_tag
                if TloggerConf.instance.is_show_source?
                  tag << " - "
                  tag << find_caller
                end
                args[0] = "[#{tag.join}] #{args[0]}"
                @tlogger.send(mtd,*args,&block)
              end
            elsif TloggerConf.is_tag_active?(@tag)
              intDebug("Tagged output...")
              tag = []
              tag << @tag
              if TloggerConf.instance.is_show_source?
                tag << " - "
                tag << find_caller
              end
              args[0] = "[#{tag.join}] #{args[0]}"
              @tlogger.send(mtd,*args,&block)
              
            elsif TloggerConf.is_auto_tag_on?
              intDebug("auto_tag is on...")
              args = tag_class(*args)
              @tlogger.send(mtd,*args,&block)

            end
            
          end

        else
          intDebug("Not proxy method for logger. Pass to logger to handle. (#{mtd})")
          ## not the debug, info, warn and error method, no need change message
          @tlogger.send(mtd, *args, &block)
        end


      elsif TloggerConf.instance.respond_to?(mtd)
        # redirect the config method to make it consistent API
        intDebug("Redirect to TloggerConf for consistancy (#{mtd})")
        TloggerConf.send(mtd, *args, &block)
      else
        intDebug("Call method_missing parent to handle method '#{mtd}'")
        super
      end
    end
    # end method_missing
    # 

    private
    def tag_class(*args)
      args[0] = "[#{find_caller}] #{args[0]}"
      args
    end
    # end tag_class()
    #
  
    def find_caller
      caller.each do |c|
        next if c =~ /tlogger.rb/
        @cal = c
        break
      end

      if @cal.nil? or @cal.empty?
        @cal = caller[0] 
      end 
     
      # reduce it down to last two folder?
      sp = @cal.split(File::SEPARATOR)
      if sp.length > 1
        msg = "/#{sp[-2]}/#{sp[-1]}" 
      else
        msg = sp[-1]
      end
      
      msg
      
      #wd = Dir.getwd
      #indx = @cal =~ /#{wd}/
      #if indx != nil
      #  @scal = []
      #  @scal << @cal[0..indx]
      #  @scal << @cal[indx+wd.length..-1]
      #  @scal.join
      #else
      #  @cal
      #end
    end
    # end find_caller
    #
  end
  # 
  # end class definition
  #


  def with_tag(tag,&block)
    if block
      TloggerConf.instance.set_scoped_tag(tag) #if not TloggerConf.instance.disabled_tags.include?(tag)
      block.call
      TloggerConf.instance.clear_scoped_tag
    end
  end
  
end