# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.
# 
# Some parts are
# Copyright 2004-2007, wxRuby development team
# released under the MIT-like wxRuby2 license

# A platform-independent image; can be manipulated more extensively than
# Bitmap, but must be converted to a Bitmap for drawing.

module Wx
  class Image

    # restore the overloaded method
    def set_rgb_data(data, *size)
      if size.empty?
        height = self.get_size.height
        width = self.get_size.width
      else
        width, height = size
        if Wx::Size === width
          height = width.height
          width = width.width
        end
      end
      set_rgb_data_with_size(data, width, height)
    end

    # alias for backward compatibility
    alias :get_data :get_rgb_data
    alias :set_data :set_rgb_data

    # Load the type-guessing hash from Wx::Bitmap
    require_relative './bitmap'
    BITMAP_TYPE_GUESS = Wx::Bitmap::BITMAP_TYPE_GUESS

    # Load a new image from an IO-like object that supports "read"
    def self.read(an_io, type_or_mime, index = -1)
      img = new
      img.load_stream(an_io, type_or_mime, index)
      img
    end

    # Create a new image from Wx::Bitmap, preserving mask information
    def self.from_bitmap(bmp)
      bmp.convert_to_image
    end

    # Ruby methods that switch class are conventionally named to_foo
    def to_bitmap
      Wx::Bitmap.from_image(self)
    end

    # Redefine the initialize method so it raises an exception if a
    # non-existent file is given to the constructor; otherwise, wx Widgets
    # just carries on with an invalid image, which may cause faults
    # later. Also, if loading from a file, and the type is not specified,
    # try to guess it from the filename extension
    wx_init = self.instance_method(:initialize)
    wx_redefine_method(:initialize) do | *args |
      if args[0].kind_of? String
        if not File.exist?( File.expand_path(args[0]) )
          Kernel.raise(ArgumentError, "Image file does not exist: #{args[0]}")
        end
        # If type not specified, try to guess it from the file extension
        if not args[1] and file_ext = args[0][/\w+$/]
          args[1] = BITMAP_TYPE_GUESS[file_ext.downcase]
        end
      end
      wx_init.bind(self).call(*args)
    end

    # Convert to bitmap
    def convert_to_bitmap
      Wx::Bitmap.from_image(self)
    end

    module Histogram

      def make_key(r, g, b)
        (r << 16) | (g << 8) | b
      end

      def find_first_unused_colour(r=1, g=0, b=0)
        while self.has_key?(make_key(r,g,b))
          if r >= 254
            r = 0
            if g >= 254
              return nil if b >= 254
              g = 0
              b += 1
            else
              g += 1
            end
          else
            r += 1
          end
        end
        [r, g, b]
      end

    end

    wx_compute_histogram = instance_method :compute_histogram
    wx_redefine_method :compute_histogram do
      hist_hash = wx_compute_histogram.bind(self).call
      hist_hash.extend Histogram
      hist_hash
    end

    # import flattened nested classes
    HSVValue = Wx::HSVValue
    RGBValue = Wx::RGBValue

  end

  class HSVValue

    # More informative output when converted to string
    def to_s
      "#<Wx::Image::HSVValue: (#{self.hue}, #{self.saturation}, #{self.value})>"
    end

    def inspect
      to_s
    end

    # make HSVValue usable for parallel assignments like `x, y = pt`
    def to_ary
      [self.hue, self.saturation, self.value]
    end

    def to_rgb
      Image::hsv_to_rgb(self)
    end

  end

  class RGBValue

    # More informative output when converted to string
    def to_s
      "#<Wx::Image::RGBValue: (#{self.red}, #{self.green}, #{self.blue})>"
    end

    def inspect
      to_s
    end

    # make RGBValue usable for parallel assignments like `x, y = pt`
    def to_ary
      [self.red, self.green, self.blue]
    end

    def to_hsv
      Image::rgb_to_hsv(self)
    end

  end

  def self.Image(name, bmp_type = nil, *rest, art_path: nil, art_section: nil)
    unless art_path && art_section
      caller_path = caller_locations(1).first.absolute_path || caller_locations(1).first.path
      art_path = File.dirname(caller_path) unless art_path
      art_section = File.basename(caller_path, '.*') unless art_section
    end
    art_file = ArtLocator.find_art(name, art_type: :image, art_path: art_path, art_section: art_section, bmp_type: bmp_type)
    ::Kernel.raise ArgumentError, "Cannot locate art file for #{name}:Image" unless art_file
    Image.new(art_file, bmp_type, *rest)
  end
end
