#!/usr/bin/ruby -w
# Encoding: UTF-8
# frozen_string_literal: true
# =========================================================================== #
begin
  require 'origami'
rescue LoadError; end

require 'gtk2'

include Gtk

require 'gtk_paradise/widgets/gtk2/pdfwalker/menu'
require 'gtk_paradise/widgets/gtk2/pdfwalker/about'
require 'gtk_paradise/widgets/gtk2/pdfwalker/file'
require 'gtk_paradise/widgets/gtk2/pdfwalker/hexview'
require 'gtk_paradise/widgets/gtk2/pdfwalker/treeview'
require 'gtk_paradise/widgets/gtk2/pdfwalker/textview'
require 'gtk_paradise/widgets/gtk2/pdfwalker/imgview'
require 'gtk_paradise/widgets/gtk2/pdfwalker/config'
require 'gtk_paradise/widgets/gtk2/pdfwalker/properties'
require 'gtk_paradise/widgets/gtk2/pdfwalker/xrefs'
require 'gtk_paradise/widgets/gtk2/pdfwalker/signing'

module PDFWalker

class Walker < Window
    
  require 'gtk_paradise/requires/require_the_base_module.rb'
  include ::Gtk::BaseModule

  attr_reader :treeview
  attr_reader :hexview
  attr_reader :objectview
  attr_reader :explorer_history
  attr_reader :config
  attr_reader :filename

  # ========================================================================= #
  # === PDFWalker::Walker.start
  # ========================================================================= #
  def self.start(file = nil)
    Gtk.init
    Walker.new(file)
    Gtk.main
  end

  # ========================================================================= #
  # === initialize
  # ========================================================================= #
  def initialize(target_file = nil)
    super('PDF Walker')

    @config = Walker::Config.new
    @opened = nil

    @last_search_result = []
    @last_search =
      {
         expr:   '',
         regexp: false,
         type:  :body
      }
    @explorer_history = []
    init_interface
    open(target_file)
  end

  # ========================================================================= #
  # === error
  # ========================================================================= #
  def error(i)
    dialog = ::Gtk::MessageDialog.new(self,
               ::Gtk::Dialog::DESTROY_WITH_PARENT,
               ::Gtk::MessageDialog::ERROR,
               ::Gtk::MessageDialog::BUTTONS_CLOSE,
               i
             )
    dialog.run
    dialog.destroy
  end

  # ========================================================================= #
  # === reload
  # ========================================================================= #
  def reload
    @treeview.load(@opened) if @opened
  end

  # ========================================================================= #
  # === search
  # ========================================================================= #
  def search
    dialog = ::Gtk::Dialog.new('Search...',
      self,
      ::Gtk::Dialog::MODAL | ::Gtk::Dialog::DESTROY_WITH_PARENT,
      [::Gtk::Stock::FIND,   ::Gtk::Dialog::RESPONSE_OK],
      [::Gtk::Stock::CANCEL, ::Gtk::Dialog::RESPONSE_CANCEL]
    )
    entry = gtk_entry
    entry.signal_connect(:activate) {
      dialog.response(::Gtk::Dialog::RESPONSE_OK)
    }
    entry.text = @last_search[:expr]

    button_bydata = Gtk::RadioButton.new("In object body")
    button_byname = Gtk::RadioButton.new(button_bydata, "In object name")
    button_regexp = gtk_check_button('Regular expression')

    button_bydata.set_active(true) if @last_search[:type] == :body
    button_byname.set_active(true) if @last_search[:type] == :name
    button_regexp.set_active(@last_search[:regexp])

    hbox = gtk_hbox
    hbox.pack_start(gtk_label('Search for expression '))
    hbox.pack_start(entry)

    dialog.vbox.pack_start(hbox)
    dialog.vbox.pack_start(button_bydata)
    dialog.vbox.pack_start(button_byname)
    dialog.vbox.pack_end(button_regexp)

    dialog.signal_connect(:response) { |_widget, response|
      if response != Gtk::Dialog::RESPONSE_OK
        dialog.destroy
        next
      end
      search_document(
        entry.text, regexp: button_regexp.active?, type: (button_bydata.active? ? 'body' : 'name')
      )
    }
    dialog.show_all
  end
  # === goto_catalog
  def goto_catalog
    @treeview.goto(@opened.Catalog.reference)
  end

  # === goto_docinfo
  def goto_docinfo
    @treeview.goto(@opened.document_info.reference) if @opened.document_info?
  end

  # === goto_metadata
  def goto_metadata
    @treeview.goto(@opened.Catalog.Metadata.reference) if @opened.metadata?
  end

  # === goto_object
  def goto_object
     dialog = Gtk::Dialog.new('Jump to object...',
                        self,
                        Gtk::Dialog::MODAL | Gtk::Dialog::DESTROY_WITH_PARENT,
                        [Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK],
                        [Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL]
            )

            entry = gtk_entry
            entry.signal_connect(:activate) { dialog.response(Gtk::Dialog::RESPONSE_OK) }

            dialog.vbox.pack_start gtk_label('Object number: ')
            dialog.vbox.pack_start entry
            dialog.show_all

            no = 0
            dialog.run { |response|
              no = entry.text.to_i if response == Gtk::Dialog::RESPONSE_OK
              dialog.destroy
            }

            return unless no > 0

            obj = @opened[no]
            if obj.nil?
              error("Object #{no} not found.")
            else
              @treeview.goto(obj)
            end
        end

        private

        # === init_interface
        def init_interface
            signal_connect(:destroy) {
              @config.save
              ::Gtk.main_quit
            }

            add_events(Gdk::Event::KEY_RELEASE_MASK)
            signal_connect('key_release_event') { |_w, event|
                if event.keyval == Gdk::Keyval::GDK_F1 then about
                elsif event.keyval == Gdk::Keyval::GDK_Escape and @opened and not @explorer_history.empty?
                    @treeview.goto(@explorer_history.pop)
                end
            }

            create_menus
            create_treeview
            create_hexview
            create_objectview
            create_panels
            create_statusbar

            @vbox = Gtk::VBox.new
            @vbox.pack_start(@menu, false, false)
            @vbox.pack_start(@hpaned)
            @vbox.pack_end(@statusbar, false, false)

            add @vbox

            set_default_size(self.screen.width * 0.5, self.screen.height * 0.5)
            show_all
        end

        def search_document(expr, regexp: false, type: 'body')
            search =
            {
                expr: expr,
                regexp: regexp,
                type: type,
            }

            if search == @last_search
                @last_search_result.push @last_search_result.shift
                results = @last_search_result
            else
                expr = Regexp.new(expr) if search[:regexp]

                results =
                    if search[:type] == 'body'
                        @opened.grep(expr)
                    else
                        @opened.ls(expr, follow_references: false)
                    end

                @last_search = search
            end

            goto_next_search_result(results)
        end

        # === goto_next_search_result
        def goto_next_search_result(results)
            if results.empty?
              error('No result could be found.')
            else
                if results != @last_search_result
                    # Reset the previous search highlighting.
                    @last_search_result.each do |obj| @treeview.highlight(obj, nil) end
                    # Highlight the new results.
                    results.each { |obj|
                      @treeview.highlight(obj, 'lightpink')
                    }
                    @last_search_result = results
                end

                @treeview.goto(results.first, follow_references: false)
            end
        end

      # === create_panels
      def create_panels
        @hpaned = gtk_hpaned

        @treepanel = gtk_scrolled_window(@treeview) { :automatic_policy }
        @vpaned = gtk_vpaned
        @vpaned.pack1(@objectview, true, false)
        # Add @hexview, which is an instance of class DumpView.
        @vpaned.pack2(@hexview, true, false)
        @hpaned.pack1(@treepanel, true, false)
        @hpaned.pack2(@vpaned, true, false)
      end

      # === create_statusbar
      def create_statusbar
        @statusbar = gtk_statusbar
        @main_context = @statusbar.get_context_id 'Main'
        @statusbar.push(@main_context, 'No file selected')
      end

      def start_profiling
        if @help_menu_profile.active?
          require 'ruby-prof'
          RubyProf.start
        end

        result = yield

        if @help_menu_profile.active?
          result = RubyProf.stop
          multiprinter = RubyProf::MultiPrinter.new(result)
          Dir.mkdir(@config.profile_output_dir) unless Dir.exist?(@config.profile_output_dir) 
          multiprinter.print(path: @config.profile_output_dir, profile: File.basename(filename))
        end
        result
      end
    end
end

if __FILE__ == $0
  PDFWalker::Walker.start
end