# frozen_string_literal: false
#
# tk/image.rb : treat Tk image objects
#

require 'tk' unless defined?(Tk)

class TkImage<TkObject
  include Tk

  TkCommandNames = ['image'.freeze].freeze

  Tk_IMGTBL = TkCore::INTERP.create_table

  (Tk_Image_ID = ['i'.freeze, '00000']).instance_eval{
    @mutex = Mutex.new
    def mutex; @mutex; end
    freeze
  }

  TkCore::INTERP.init_ip_env{
    Tk_IMGTBL.mutex.synchronize{ Tk_IMGTBL.clear }
  }

  def self.new(keys=nil)
    if keys.kind_of?(Hash)
      name = nil
      if keys.key?(:imagename)
        name = keys[:imagename]
      elsif keys.key?('imagename')
        name = keys['imagename']
      end
      if name
        if name.kind_of?(TkImage)
          obj = name
        else
          name = _get_eval_string(name)
          obj = nil
          Tk_IMGTBL.mutex.synchronize{
            obj = Tk_IMGTBL[name]
          }
        end
        if obj
          if !(keys[:without_creating] || keys['without_creating'])
            keys = _symbolkey2str(keys)
            keys.delete('imagename')
            keys.delete('without_creating')
            obj.instance_eval{
              tk_call_without_enc('image', 'create',
                                  @type, @path, *hash_kv(keys, true))
            }
          end
          return obj
        end
      end
    end
    (obj = self.allocate).instance_eval{
      Tk_IMGTBL.mutex.synchronize{
        initialize(keys)
        Tk_IMGTBL[@path] = self
      }
    }
    obj
  end

  def initialize(keys=nil)
    @path = nil
    without_creating = false
    if keys.kind_of?(Hash)
      keys = _symbolkey2str(keys)
      @path = keys.delete('imagename')
      without_creating = keys.delete('without_creating')
    end
    unless @path
      Tk_Image_ID.mutex.synchronize{
        @path = Tk_Image_ID.join(TkCore::INTERP._ip_id_)
        Tk_Image_ID[1].succ!
      }
    end
    unless without_creating
      tk_call_without_enc('image', 'create',
                          @type, @path, *hash_kv(keys, true))
    end
  end

  def delete
    Tk_IMGTBL.mutex.synchronize{
      Tk_IMGTBL.delete(@id) if @id
    }
    tk_call_without_enc('image', 'delete', @path)
    self
  end
  def height
    number(tk_call_without_enc('image', 'height', @path))
  end
  def inuse
    bool(tk_call_without_enc('image', 'inuse', @path))
  end
  def itemtype
    tk_call_without_enc('image', 'type', @path)
  end
  def width
    number(tk_call_without_enc('image', 'width', @path))
  end

  def TkImage.names
    Tk_IMGTBL.mutex.synchronize{
      Tk.tk_call_without_enc('image', 'names').split.collect!{|id|
        (Tk_IMGTBL[id])? Tk_IMGTBL[id] : id
      }
    }
  end

  def TkImage.types
    Tk.tk_call_without_enc('image', 'types').split
  end
end

class TkBitmapImage<TkImage
  def __strval_optkeys
    super() + ['maskdata', 'maskfile']
  end
  private :__strval_optkeys

  def initialize(*args)
    @type = 'bitmap'
    super(*args)
  end
end

# A photo is an image whose pixels can display any color or be transparent.
# At present, only GIF and PPM/PGM formats are supported, but an interface
# exists to allow additional image file formats to be added easily.
#
# This class documentation is a copy from the original Tcl/Tk at
# http://www.tcl.tk/man/tcl8.5/TkCmd/photo.htm with some rewritten parts.
class TkPhotoImage<TkImage
  NullArgOptionKeys = [ "shrink", "grayscale" ]

  def _photo_hash_kv(keys)
    keys = _symbolkey2str(keys)
    NullArgOptionKeys.collect{|opt|
      if keys[opt]
        keys[opt] = None
      else
        keys.delete(opt)
      end
    }
    keys.collect{|k,v|
      ['-' << k, v]
    }.flatten
  end
  private :_photo_hash_kv

  # Create a new image with the given options.
  # == Examples of use :
  # === Create an empty image of 300x200 pixels
  #
  #		image = TkPhotoImage.new(:height => 200, :width => 300)
  #
  # === Create an image from a file
  #
  #		image = TkPhotoImage.new(:file: => 'my_image.gif')
  #
  # == Options
  # Photos support the following options:
  # * :data
  #   Specifies the contents of the image as a string.
  # * :format
  #   Specifies the name of the file format for the data.
  # * :file
  #   Gives the name of a file that is to be read to supply data for the image.
  # * :gamma
  #   Specifies that the colors allocated for displaying this image in a window
  #   should be corrected for a non-linear display with the specified gamma
  #   exponent value.
  # * height
  #   Specifies the height of the image, in pixels. This option is useful
  #   primarily in situations where the user wishes to build up the contents of
  #   the image piece by piece. A value of zero (the default) allows the image
  #   to expand or shrink vertically to fit the data stored in it.
  # * palette
  #   Specifies the resolution of the color cube to be allocated for displaying
  #   this image.
  # * width
  #   Specifies the width of the image, in pixels. This option is useful
  #   primarily in situations where the user wishes to build up the contents of
  #   the image piece by piece. A value of zero (the default) allows the image
  #   to expand or shrink horizontally to fit the data stored in it.
  def initialize(*args)
    @type = 'photo'
    super(*args)
  end

  # Blank the image; that is, set the entire image to have no data, so it will
  # be displayed as transparent, and the background of whatever window it is
  # displayed in will show through.
  def blank
    tk_send_without_enc('blank')
    self
  end

  def cget_strict(option)
    case option.to_s
    when 'data', 'file'
      tk_send 'cget', '-' << option.to_s
    else
      tk_tcl2ruby(tk_send('cget', '-' << option.to_s))
    end
  end

  # Returns the current value of the configuration option given by option.
  # Example, display name of the file from which <tt>image</tt> was created:
  # 	puts image.cget :file
  def cget(option)
    unless TkConfigMethod.__IGNORE_UNKNOWN_CONFIGURE_OPTION__
      cget_strict(option)
    else
      begin
        cget_strict(option)
      rescue => e
        if current_configinfo.has_key?(option.to_s)
          # error on known option
          fail e
        else
          # unknown option
          nil
        end
      end
    end
  end

  # Copies a region from the image called source to the image called
  # destination, possibly with pixel zooming and/or subsampling. If no options
  # are specified, this method copies the whole of source into destination,
  # starting at coordinates (0,0) in destination. The following options may be
  # specified:
  #
  # * :from [x1, y1, x2, y2]
  #   Specifies a rectangular sub-region of the source image to be copied.
  #   (x1,y1) and (x2,y2) specify diagonally opposite corners of the rectangle.
  #   If x2 and y2 are not specified, the default value is the bottom-right
  #   corner of the source image. The pixels copied will include the left and
  #   top edges of the specified rectangle but not the bottom or right edges.
  #   If the :from option is not given, the default is the whole source image.
  # * :to [x1, y1, x2, y2]
  #   Specifies a rectangular sub-region of the destination image to be
  #   affected. (x1,y1) and (x2,y2) specify diagonally opposite corners of the
  #   rectangle. If x2 and y2 are not specified, the default value is (x1,y1)
  #   plus the size of the source region (after subsampling and zooming, if
  #   specified). If x2 and  y2 are specified, the source region will be
  #   replicated if necessary to fill the destination region in a tiled fashion.
  # * :shrink
  #   Specifies that the size of the destination image should be reduced, if
  #   necessary, so that the region being copied into is at the bottom-right
  #   corner of the image. This option will not affect the width or height of
  #   the image if the user has specified a non-zero value for the :width or
  #   :height configuration option, respectively.
  # * :zoom [x, y]
  #   Specifies that the source region should be magnified by a factor of x
  #   in the X direction and y in the Y direction. If y is not given, the
  #   default value is the same as x. With this option, each pixel in the
  #   source image will be expanded into a block of x x y pixels in the
  #   destination image, all the same color. x and y must be greater than 0.
  # * :subsample [x, y]
  #   Specifies that the source image should be reduced in size by using only
  #   every xth pixel in the X direction and yth pixel in the Y direction.
  #   Negative values will cause the image to be flipped about the Y or X axes,
  #   respectively. If y is not given, the default value is the same as x.
  # * :compositingrule rule
  #   Specifies how transparent pixels in the source image are combined with
  #   the destination image. When a compositing rule of <tt>overlay</tt> is set,
  #   the old  contents of the destination image are visible, as if the source
  #   image were  printed on a piece of transparent film and placed over the
  #   top of the  destination. When a compositing rule of <tt>set</tt> is set,
  #   the old contents of  the destination image are discarded and the source
  #   image is used as-is. The default compositing rule is <tt>overlay</tt>.
  def copy(src, *opts)
    if opts.size == 0
      tk_send('copy', src)
    elsif opts.size == 1 && opts[0].kind_of?(Hash)
      tk_send('copy', src, *_photo_hash_kv(opts[0]))
    else
      # for backward compatibility
      args = opts.collect{|term|
        if term.kind_of?(String) && term.include?(?\s)
          term.split
        else
          term
        end
      }.flatten
      tk_send('copy', src, *args)
    end
    self
  end

  # Returns image data in the form of a string. The following options may be
  # specified:
  # * :background color
  #   If the color is specified, the data will not contain any transparency
  #   information. In all transparent pixels the color will be replaced by the
  #   specified color.
  # * :format format-name
  #   Specifies the name of the image file format handler to be used.
  #   Specifically, this subcommand searches for the first handler whose name
  #   matches an initial substring of format-name and which has the capability
  #   to read this image data. If this option is not given, this subcommand
  #   uses the first handler that has the capability to read the image data.
  # * :from [x1, y1, x2, y2]
  #   Specifies a rectangular region of imageName to be returned. If only x1
  #   and y1 are specified, the region extends from (x1,y1) to the bottom-right
  #   corner of imageName. If all four coordinates are given, they specify
  #   diagonally opposite corners of the rectangular region, including x1,y1
  #   and excluding x2,y2. The default, if this option is not given, is the
  #   whole image.
  # * :grayscale
  #   If this options is specified, the data will not contain color information.
  #   All pixel data will be transformed into grayscale.
  def data(keys={})
    tk_split_list(tk_send('data', *_photo_hash_kv(keys)))
  end

  # Returns the color of the pixel at coordinates (x,y) in the image as a list
  # of three integers between 0 and 255, representing the red, green and blue
  # components respectively.
  def get(x, y)
    tk_send('get', x, y).split.collect{|n| n.to_i}
  end

  def put(data, *opts)
    if opts.empty?
      tk_send('put', data)
    elsif opts.size == 1 && opts[0].kind_of?(Hash)
      tk_send('put', data, *_photo_hash_kv(opts[0]))
    else
      # for backward compatibility
      tk_send('put', data, '-to', *opts)
    end
    self
  end

  def read(file, *opts)
    if opts.size == 0
      tk_send('read', file)
    elsif opts.size == 1 && opts[0].kind_of?(Hash)
      tk_send('read', file, *_photo_hash_kv(opts[0]))
    else
      # for backward compatibility
      args = opts.collect{|term|
        if term.kind_of?(String) && term.include?(?\s)
          term.split
        else
          term
        end
      }.flatten
      tk_send('read', file, *args)
    end
    self
  end

  def redither
    tk_send 'redither'
    self
  end

  # Returns a boolean indicating if the pixel at (x,y) is transparent.
  def get_transparency(x, y)
    bool(tk_send('transparency', 'get', x, y))
  end

  # Makes the pixel at (x,y) transparent if <tt>state</tt> is true, and makes
  # that pixel opaque otherwise.
  def set_transparency(x, y, state)
    tk_send('transparency', 'set', x, y, state)
    self
  end

  def write(file, *opts)
    if opts.size == 0
      tk_send('write', file)
    elsif opts.size == 1 && opts[0].kind_of?(Hash)
      tk_send('write', file, *_photo_hash_kv(opts[0]))
    else
      # for backward compatibility
      args = opts.collect{|term|
        if term.kind_of?(String) && term.include?(?\s)
          term.split
        else
          term
        end
      }.flatten
      tk_send('write', file, *args)
    end
    self
  end
end
