# Copyright (c) 2007-2021 Andy Maleh
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

require 'glimmer/swt/properties'
require 'glimmer/swt/swt_proxy'
require 'glimmer/swt/display_proxy'
require 'glimmer/swt/color_proxy'
require 'glimmer/swt/font_proxy'

module Glimmer
  module SWT
    module Custom
      # Represents a shape (graphics) to be drawn on a control/widget/canvas/display
      # swt_widget returns the parent (e.g. a `canvas` WidgetProxy), equivalent to `parent.swt_widget`
      # That is because Shape is drawn on a parent as graphics and doesn't have an SWT widget for itself
      class Shape
        include Properties
        # TODO support textExtent sized shapes nested within text/string
        # TODO support a Pattern DSL for methods that take Pattern arguments
        
        class << self
          def valid?(parent, keyword, *args, &block)
            gc_instance_methods.include?(method_name(keyword, args))
          end
          
          def gc_instance_methods
            org.eclipse.swt.graphics.GC.instance_methods.map(&:to_s)
          end
          
          def arg_options(args, extract: false)
            arg_options_method = extract ? :pop : :last
            options = args.send(arg_options_method) if args.last.is_a?(Hash)
            options.nil? ? {} : options.symbolize_keys
          end
          
          def method_name(keyword, args)
            keyword = keyword.to_s
            gradient = 'gradient_' if arg_options(args)[:gradient]
            round = 'round_' if arg_options(args)[:round]
            gc_instance_method_name_prefix = !['polyline', 'point', 'image', 'focus'].include?(keyword) && (arg_options(args)[:fill] || arg_options(args)[:gradient]) ? 'fill_' : 'draw_'
            "#{gc_instance_method_name_prefix}#{gradient}#{round}#{keyword}"
          end
        end
        
        attr_reader :parent, :name, :args, :options, :swt_widget, :paint_listener_proxy
        
        def initialize(parent, keyword, *args, &property_block)
          @parent = parent
          @name = keyword
          @method_name = self.class.method_name(keyword, args)
          @options = self.class.arg_options(args, extract: true)
          @args = args
          @swt_widget = parent.respond_to?(:swt_display) ? parent.swt_display : parent.swt_widget
          @properties = {}
          @parent.shapes << self
          post_add_content if property_block.nil?
        end
        
        def draw?
          !fill?
        end
        
        def fill?
          @options[:fill]
        end
        
        def gradient?
          @options[:gradient]
        end
        
        def round?
          @options[:round]
        end
        
        def post_add_content
          event_handler = lambda do |event|
            @properties['background'] = [@parent.background] if fill? && !@properties.keys.map(&:to_s).include?('background')
            @properties['foreground'] = [@parent.foreground] if draw? && !@properties.keys.map(&:to_s).include?('foreground')
            @properties.each do |property, args|
              method_name = attribute_setter(property)
              apply_property_arg_conversions(method_name, args)
              event.gc.send(method_name, *args)
            end
            apply_shape_arg_conversions(@method_name, @args)
            apply_shape_arg_defaults(@method_name, @args)
            tolerate_shape_extra_args(@method_name, @args)
            event.gc.send(@method_name, *@args)
          end
          if parent.respond_to?(:swt_display)
            @paint_listener_proxy = @parent.on_swt_paint(&event_handler)
          else
            @paint_listener_proxy = @parent.on_paint_control(&event_handler)
          end
        end
        
        def apply_property_arg_conversions(method_name, args)
          the_java_method = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.detect {|m| m.name == method_name}
          if (args.first.is_a?(Symbol) || args.first.is_a?(String))
            if the_java_method.parameter_types.first == Color.java_class
              args[0] = ColorProxy.new(args[0])
            end
            if the_java_method.parameter_types.first == Java::int.java_class
              args[0] = SWTProxy.constant(args[0])
            end
          end
          if args.first.is_a?(ColorProxy)
            args[0] = args[0].swt_color
          end
          if args.first.is_a?(Hash) && the_java_method.parameter_types.first == Font.java_class
            args[0] = FontProxy.new(args[0])
          end
          if args.first.is_a?(FontProxy)
            args[0] = args[0].swt_font
          end
          if ['setBackgroundPattern', 'setForegroundPattern'].include?(method_name.to_s)
            args.each_with_index do |arg, i|
              if arg.is_a?(Symbol) || arg.is_a?(String)
                args[i] = ColorProxy.new(arg).swt_color
              elsif arg.is_a?(ColorProxy)
                args[i] = arg.swt_color
              end
            end
            new_args = [DisplayProxy.instance.swt_display] + args
            args[0] = org.eclipse.swt.graphics.Pattern.new(*new_args)
            args[1..-1] = []
          end
        end
        
        def apply_shape_arg_conversions(method_name, args)
          if args.size > 1 && (method_name.include?('polygon') || method_name.include?('polyline'))
            args[0] = args.dup
            args[1..-1] = []
          end
        end
        
        def apply_shape_arg_defaults(method_name, args)
          if method_name.include?('round_rectangle') && args.size.between?(4, 5)
            (6 - args.size).times {args << 60}
          elsif method_name.include?('rectangle') && gradient? && args.size == 4
            args << true
          elsif (method_name.include?('text') || method_name.include?('string')) && !@properties.keys.map(&:to_s).include?('background') && args.size == 3
            args << true
          end
          if method_name.include?('image') && args.first.is_a?(String)
            args[0] = ImageProxy.new(args[0])
          end
          if method_name.include?('image') && args.first.is_a?(ImageProxy)
            args[0] = args[0].swt_image
          end
        end
                
        # Tolerates shape extra args added by user by mistake
        # (e.g. happens when switching from round rectangle to a standard one without removing all extra args)
        def tolerate_shape_extra_args(method_name, args)
          the_java_method_arg_count = org.eclipse.swt.graphics.GC.java_class.declared_instance_methods.select do |m|
            m.name == method_name.camelcase(:lower)
          end.map(&:parameter_types).map(&:size).max
          if args.size > the_java_method_arg_count
            args[the_java_method_arg_count..-1] = []
          end
        end
                
        def has_attribute?(attribute_name, *args)
          self.class.gc_instance_methods.include?(attribute_setter(attribute_name))
        end
  
        def set_attribute(attribute_name, *args)
          @properties[attribute_name] = args
        end
  
        def get_attribute(attribute_name)
          @properties.symbolize_keys[attribute_name.to_s.to_sym]
        end
    
      end
      
    end
    
  end
  
end