#
#   Copyright (C) 2006 Eriko Sato
#
#   This program is free software; you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation; either version 2, or (at your option)
#   any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#

module Kz
  module ActionSandbox
    include GetText
    extend GetText
  end

  module Actions
    @@actions = {}

    class << self
      def init_actions(kz)
        @@actions[kz] = []
        kz.signal_connect_after("destroy") do
          @@actions.delete(kz)
        end
        load_actions.each do |sandbox|
          register_actions(kz, sandbox)
        end

        kz.update_gesture_items
      rescue Exception
        Kz.print_error($!)
      end

      def reload_actions(kz)
        @@actions[kz].delete_if do |name|
          action = kz.actions.get_action(name)
          kz.disconnect_action(action)
          kz.actions.remove_action(action)
          true
        end

        init_actions(kz)
        @@actions[kz].each do |name|
          action = kz.actions.get_action(name)
          kz.connect_action(action)
        end
      end

      def install_action(kz, name)
        if name.include?(File::SEPARATOR) and File.extname(name) == ".rb"
          action = name
        else
          action = search_action(name)
        end
        if action
          sandbox = load_action(action)
          if sandbox
            register_actions(kz, sandbox)
            kz.update_gesture_items
          end
        end
      rescue Exception
        Kz.print_error($!)
      end

      def search_file(first, *rest)
        search_dirs.each do |path|
          full_path = File.join(path, first, *rest)
          return full_path if File.exist?(full_path)
        end
        nil
      end

      def search_dirs
        search_dirs = $LOAD_PATH.collect do |path|
          File.join(File.expand_path(path), "kz", "actions")
        end
        search_dirs.unshift(Kz::ACTIONS_DIR)
        search_dirs
      end

      private
      def to_class_name(name)
        name.gsub(/(?:\A|_|\-)([a-z])/) do |x|
          $1.upcase
        end
      end

      def search_action(name)
        search_dirs.each do |dir|
          action = File.join(dir, "#{name}.rb")
          return action if File.exist?(action)
        end
        nil
      end

      def load_action(action)
        sandbox = Module.new
        source = File.open(action) {|f| f.read}
        sandbox.module_eval(<<-EOA, action, -1)
extend ::Kz::ActionSandbox
module_function
#{source}
EOA
        sandbox
      rescue Exception
        Kz.print_error($!)
        nil
      end

      def load_actions
        actions = []
        action_names = {}
        search_dirs.each do |dir|
          next unless File.exist?(dir)
          Dir[File.join(dir, "*.rb")].each do |action|
            action_name = File.basename(action)
            next if action_names.has_key?(action_name)
            action_names[action_name] = true
            actions << action
          end
        end

        sandboxes = []
        actions.sort_by {|action| File.basename(action)}.each do |action|
          sandboxes << load_action(action)
        end
        sandboxes.compact
      end

      def register_action_to_internal_actions(kz, name)
        action = kz.actions.get_action(name)
        if action
          kz.actions.remove_action(action)
          Kz.print_warning("override exist action: %s" % name)
        end
        @@actions[kz] << name
      end

      def register_actions(kz, sandbox)
        actions, toggle_actions, radio_actions = collect_action_infos(sandbox)
        acts = to_gtk_actions(actions, kz)
        kz.actions.add_actions(acts)
        acts = to_gtk_actions(toggle_actions, kz, true)
        kz.actions.add_toggle_actions(acts)
        add_radio_actions(kz.actions, radio_actions, kz)
      end

      def collect_action_infos(sandbox)
        actions = []
        toggle_actions = []
        radio_actions = []
        sandbox.methods(false).each do |name|
          case name
          when /_config$/
            # ignore
          when /^act_(radio_.+)$/
            radio_actions << [sandbox, to_class_name($1), name]
          when /^act_(toggle_.+)$/
            toggle_actions << [sandbox, to_class_name($1), name]
          when /^act_(.+)$/
            actions << [sandbox, to_class_name($1), name]
          end
        end
        [actions, toggle_actions, radio_actions]
      end
      
      def to_gtk_actions(actions, kz, toggle=false)
        actions.collect do |sandbox, name, action|
          register_action_to_internal_actions(kz, name)
          
          config = {:label => name}
          config_method = "#{action}_config"
          if sandbox.respond_to?(config_method)
            sandbox.__send__(config_method, config, kz)
          end
          callback = sandbox.method(action)
          result = [
            name,
            config[:stock_id],
            config[:label],
            config[:accelerator],
            config[:tooltip],
            Proc.new do |group, act|
              begin
                callback.call(act, group, kz)
              rescue Exception
                Kz.print_error($!)
              end
            end
          ]
          result << config[:is_active] if toggle
          result
        end
      end
      
      def add_radio_actions(group, actions, kz)
        actions.each do |sandbox, name, action|
          register_action_to_internal_actions(kz, name)
          
          default_value = nil
          action_name = nil
          gtk_actions = methods(false).find_all do |method_name|
            /^#{action}_(?:.+)_config$/ =~ method_name
          end.collect do |config_method|
            /^act_(.*)_config$/ =~ config_method
            action_name = to_class_name($1)
            config = {:label => name}
            sandbox.__send__(config_method, config, kz)
            default_value = config[:value] if config[:default]
            [
              action_name,
              config[:stock_id],
              config[:label],
              config[:accelerator],
              config[:tooltip],
              config[:value],
            ]
          end
          
          callback = sandbox.method(action)
          group.add_radio_actions(gtk_actions, default_value) do |act, current|
            begin
              callback.call(act, current, group, kz)
            rescue Exception
              Kz.print_error($!)
            end
          end
        end
      end
    end
  end
end
