class Hide

  @@visible = {}

  def self.menu
    "
    | TODO: add docs about hiding.
    "
  end


  #  self.reset
  def self.hide_unless search, options={}
    self.hide_unless_block(options) do |l, bol, eol|
      l =~ search
    end
  end

  def self.visible
    @@visible
  end

  def self.already_hidden
    @@already_hidden
  end

  def self.hide_unless_block options={}, &block

    #    elvar.line_move_ignore_invisible = true
    #     @@already_hidden = {}
    #     @@visible = []

    @@visible = []

    # Unless we can optimize by assuming hidden regions are ok
    unless options[:optimize]
      self.reset
      # Lisp funtion defined below
      l = $el.hide_find_overlays.to_a
      l.each { |i| @@already_hidden[i] = true }
    end

    t = $el.buffer_string
    lines = t.split "\n"
    bol = 1
    left = 1
    in_non_match = false

    if options[:include_first]   # Optionally skip top line
      l = lines.shift
      eol = bol + l.size + 1
      left = eol
      bol = eol
    end

    lines.each do |l|   # For each line
      eol = bol + l.size + 1
      #      if (l =~ search) && (! @@already_hidden[bol])
      if (yield(l, bol, eol)) && (! @@already_hidden[bol])
        @@visible << bol
        # If we're at the end of non-match area, hide
        if in_non_match
          @@areas_to_hide << [left, bol]
          in_non_match = false
        end
        left = eol
      else
        in_non_match = true
        @@already_hidden[bol] = true
      end
      bol = eol
    end

    @@areas_to_hide << [left, bol] if in_non_match

    # Construct and run lisp to hide it
    elisp = "(progn\n"
    @@areas_to_hide.each do |i|
      elisp += " (overlay-put (make-overlay #{i[0]} #{i[1]}) 'invisible 'hide)\n"
    end
    $el.el4r_lisp_eval elisp + ")"

  end

  def self.char_from_user
    c = $el.read_char_exclusive
    # Treat C-0 - C-9 the same as 0-9
    c -= 67108864 if(c >= 67108912 && c <= 67108921)
    "#{c.chr}"
  end

  def self.search search=nil, options={}
    if search
      if search.class == String
        search = /#{Regexp.escape(search)}/i
      end
      self.hide_unless search, options
    else
      search = ""
    end

    $el.message "Hide.search for: #{search}"
    ch = self.char_from_user

    first_search = true

    while true
      break unless ch =~ /[-a-zA-Z ._=@\/\\#,;*<>-]/
      search += ch
      $el.message "hide-search for: #{search}"

      # If a tab, clear search and keep going
      if ch == ","
        search = ""
        ch = self.char_from_user
        next
      end

      self.hide_unless /#{Regexp.escape(search)}/i, options
      $el.recenter -3

      # Don't re-check visible regions from now on
      if first_search
        options.merge!(:optimize => true)
        first_search = false
      end

      # If only one left, go to it
      if @@visible.size == 1 && options[:expand_when_one]
        ch = "1"
        break
      end
      ch = self.char_from_user
    end

    ch = 1 if ch == "\t"   # If a tab, treat it like C-1

    # If num
    if 1 <= ch.to_i && ch.to_i <= 9
      if @@visible.size > 0
        if @@visible[ch.to_i - 1]
          $el.goto_char @@visible[ch.to_i - 1]
        else
          $el.goto_char @@visible[-1]
        end
      else
        $el.goto_line ch.to_i
      end
      #      next_line -1
      self.show
      $el.recenter 0
      # Run enter if option was passed
      if options[:press_enter]
        $el.command_execute "\C-m"
      end

    # Show all if they typed C-a
    elsif ch == "\001"
      self.show
    # Else, if control character, execute it and exit
    elsif "\C-a" <= ch && ch <= "\C-z"
      $el.command_execute ch unless ch == "\C-m"
    end
  end

  def self.keys
  end

  # Shows things hidden by .hide
  def self.show

    # Move down to visible line (this doesn't seem to happen on its own)
    while( @@already_hidden[$el.point] )
      $el.forward_line
    end

    #    @@already_hidden = {}
    # Delete hidden overlays
    $el.el4r_lisp_eval %`
      (dolist (over (overlays-in (point-min) (point-max) ))
        (when
          (overlay-get over 'invisible)
          (delete-overlay over)
        )
      )
    `
    self.reset
    @@visible = []
  end

  def self.visible
    @@visible
  end

  def self.reset
    @@areas_to_hide = []
    # Make this be stored per page, not globally
    @@already_hidden = {}
  end

  # What is this doing here??
  self.reset
  $el.add_to_invisibility_spec :hide if $el

  def self.hide_by_indent indent=nil
    indent ||= Keys.prefix
    indent = -1 if indent == :u

    # If no prefix, use indent of current line
    if indent.nil?
      indent = Line.matches(/^ */).size
      # If currently indented to that level, go one deeper
      if $el.elvar.selective_display && indent == ($el.elvar.selective_display - 1)
        indent += 2
      end
    end

    $el.set_selective_display(indent + 1)
  end

  def self.init

    return if ! $el

    # Define lisp function to get list of displayed lines
    # In case something has been done to change them
    $el.el4r_lisp_eval %`
      (defun hide-find-overlays ()
        (save-excursion
          (beginning-of-buffer)
          (let (found (res ()))
            (while (not (eq (point) (point-max)))
              (setq found nil)
              ; Save if not hidden
              (dolist (o (overlays-at (point)))
                (if (overlay-get o 'invisible)
                  (setq found t)
                )
              )
              (if found
                (push (point) res))
              (forward-line)
            )
            (reverse res)
          )
        )
      )
    `
  end

  # Reveals all hidden
  def self.reveal
    $el.widen
    self.show
    self.hide_by_indent :u   # If hidden by indent
  end

end
Hide.init
