#---
# Excerpted from "Scripted GUI Testing With Ruby",
# published by The Pragmatic Bookshelf.
# Copyrights apply to this code. It may not be used to create training material, 
# courses, books, articles, and the like. Contact us if you are in doubt.
# We make no guarantees that this code is fit for any purpose. 
# Visit http://www.pragmaticprogrammer.com/titles/idgtr for more book information.
#---
require 'java'
require 'rubygems'
require 'cheri/swing'
require 'crypt/gost'

include_class javax.swing.JOptionPane

class String
  def encrypt(pw)
    padded = pw + "\0" * [0, 8 - pw.length].max
    Crypt::Gost.new(padded).encrypt_string(self)
  rescue
    return '(wrong password)'
  end

  def decrypt(pw)
    padded = pw + "\0" * [0, 8 - pw.length].max
    Crypt::Gost.new(padded).decrypt_string(self)
  rescue
    return '(wrong password)'
  end
end

class JunqueNoteApp
  include Cheri::Swing

  def initialize
    swing[:auto]
    
    @frame = swing.frame('JunqueNote') do |f|
      size 400, 300
      box_layout f, :Y_AXIS
      
      menu_bar do
        menu('File') do
          menu_item('Open...') {on_click {open} }
          menu_item('Change Password...') {on_click {change_password} }
          menu_item('Save As...') {on_click {save_as} }
          menu_item('Exit') {on_click {exit_app} }
        end
        
        menu('Edit') do
          menu_item('Undo')                       {on_click {@text_area.text = @state.pop} }
          menu_item('Cut')                        {on_click {@state.push(@text_area.text); @text_area.cut} }
          menu_item('Copy')                       {on_click {@state.push(@text_area.text); @text_area.copy} }
          menu_item('Paste')                      {on_click {@state.push(@text_area.text); @text_area.paste} }

          menu_item('Find...')                    {on_click {find} }
          menu_item('Find Exact Case...')         {on_click {find :case} }
          menu_item('Reverse Find...')            {on_click {find :reverse} }
          menu_item('Reverse Find Exact Case...') {on_click {find :case_reverse} }
          menu_item('Find Next')                  {on_click {find @how, @term} }
        end
        
        menu('Help') do
          menu_item('About JunqueNote...') {on_click {about} }
        end
      end
      
      @text_area = text_area('Welcome to JunqueNote!') do
        on_key_pressed {|event| @state.push(@text_area.text); @dirty = true}
      end
    end

    @state = [@text_area.text]
    @frame.visible = true
  end
  
  def request_filename(button = 'Save')
    pane = JOptionPane.new \
      'Please enter a filename',
      JOptionPane::PLAIN_MESSAGE,
      JOptionPane::OK_CANCEL_OPTION,
      nil,
      [button,'Cancel'].to_java,
      button
    pane.wants_input = true
    pane.create_dialog(@frame, 'Input').show
    
    button == pane.value ? pane.input_value : nil
  end
  
  def save_as(filename = nil)
    save_name = filename || request_filename
    return if save_name.nil?
    
    @filename = save_name
    
    if @password.nil?
      @password = JOptionPane.show_input_dialog "Please assign a password"
      return if @password.nil?
      confirmation = JOptionPane.show_input_dialog "Please confirm the password"
      return if confirmation.nil?
    
      if @password != confirmation
        JOptionPane.show_message_dialog(
          @frame,
          "The password and confirmation don't match",
          "Oops",
          JOptionPane::YES_NO_OPTION)
        
        return
      end
    end
    
    File.delete @filename if File.exists? @filename
    File.open @filename, 'wb' do |f|
      plaintext = "JunqueNote\n#{@text_area.text.length}\n#{@text_area.text}"
      encrypted = plaintext.encrypt(@password)
      f.write encrypted
    end

    @dirty = false
  end

  def open
    @filename = request_filename 'Open'
    if @filename.nil?
      @frame.dispose
      return
    end

    @password = JOptionPane.show_input_dialog "Please enter the password"
    if @password.nil?
      @frame.dispose
      return
    end
    
    encrypted = File.open(@filename, 'rb') {|f| f.read}
    init, length, contents = encrypted.decrypt(@password).split($;, 3)
    
    if init == 'JunqueNote'
      contents = contents[0, length.to_i]
      @text_area.text = contents
      @state = [contents]
      @dirty = false
    else
      JOptionPane.show_message_dialog(
        @frame,
        "The password doesn't match",
        "Oops",
        JOptionPane::YES_NO_OPTION)
        
      @frame.dispose
    end
  end
  
  def change_password
    old_password = JOptionPane.show_input_dialog "Please enter the password"
    return if old_password.nil?
    
    if old_password != @password
      JOptionPane.show_message_dialog(
        @frame,
        "The password doesn't match",
        "Oops",
        JOptionPane::YES_NO_OPTION)
        
      return
    end

    new_password = JOptionPane.show_input_dialog "Please enter a password"
    return if new_password.nil?
    confirmation = JOptionPane.show_input_dialog "Please confirm the password"
    return if confirmation.nil?
  
    if new_password != confirmation
      JOptionPane.show_message_dialog(
        @frame,
        "The password and confirmation don't match",
        "Oops",
        JOptionPane::YES_NO_OPTION)
      
      return
    end
    
    @password = new_password
    
    File.delete @filename if File.exists? @filename
    File.open @filename, 'wb' do |f|
      plaintext = "JunqueNote\n#{@text_area.text.length}\n#{@text_area.text}"
      encrypted = plaintext.encrypt(@password)
      f.write encrypted
    end

    @dirty = false
  end
  
  def exit_app
    should_save = if @dirty
      0 == JOptionPane.show_confirm_dialog(
        nil,
        "Wanna save first?",
        "Quittin' time",
        JOptionPane::YES_NO_OPTION)
    else
      false
    end
    
    save_as(@filename) if should_save
    @frame.dispose
  end
  
  def about
    JOptionPane.show_message_dialog(
      @frame,
      "A hypothetical JRuby port of LockNote",
      "About JunqueNote",
      JOptionPane::INFORMATION_MESSAGE)
  end
  
  def find(how = nil, term = nil)
    term ||= JOptionPane.show_input_dialog "Please enter the search term"
    return unless term
    
    @how = how
    @term = term
    
    pattern, backwards = case how
    when :case
      [/#{term}/, false]
    when :reverse
      [/#{term.reverse}/i, true]
    when :case_reverse
      [/#{term.reverse}/, true]
    else
      [/#{term}/i, false]
    end
    
    position = @text_area.get_selection_start || 0    
    contents = @text_area.text
    
    if backwards
      contents.reverse!
      position = contents.length - position
    end
    
    next_position = contents.index pattern, position + 1
    
    if next_position
      next_position =
        contents.length -
        next_position -
        term.length if backwards
      
      @text_area.set_selection_start next_position
      @text_area.set_selection_end next_position + term.length
    end
  end
end

if __FILE__ == $0
  JunqueNoteApp.new
end
