class PSD
  # A descriptor is a block of data that describes a complex data structure of some kind.
  # It was added sometime around Photoshop 5.0 and it superceded a few legacy things such
  # as layer names and type data.
  class Descriptor
    # Store a reference to our file and initialize our data structure.
    def initialize(file)
      @file = file
      @data = {}
    end

    # Parse the descriptor. Descriptors always start with a class identifier, followed by
    # a variable number of items in the descriptor. We return the Hash that represents
    # the full data structure.
    def parse
      PSD.logger.debug "Descriptor: pos = #{@file.tell}"

      @data[:class] = parse_class
      num_items = @file.read_int

      PSD.logger.debug "Class = #{@data[:class]}, Item count = #{num_items}"

      num_items.times do |i|
        id, value = parse_key_item
        @data[id] = value
      end

      @data
    end

    private

    def parse_class
      {
        name: @file.read_unicode_string,
        id: parse_id
      }
    end

    def parse_id
      len = @file.read_int
      len == 0 ? @file.read_string(4) : @file.read_string(len)
    end

    def parse_key_item
      id = parse_id
      PSD.logger.debug "Key = #{id}"

      value = parse_item

      return id, value
    end

    def parse_item(type = nil)
      type = @file.read_string(4) if type.nil?
      PSD.logger.debug "Type = #{type}"

      value = case type
      when 'bool'         then parse_boolean
      when 'type', 'GlbC' then parse_class
      when 'Objc', 'GlbO' then Descriptor.new(@file).parse
      when 'doub'         then parse_double
      when 'enum'         then parse_enum
      when 'alis'         then parse_alias
      when 'Pth'          then parse_file_path
      when 'long'         then parse_integer
      when 'comp'         then parse_large_integer
      when 'VlLs'         then parse_list
      when 'ObAr'         then parse_object_array
      when 'tdta'         then parse_raw_data
      when 'obj '         then parse_reference
      when 'TEXT'         then @file.read_unicode_string
      when 'UntF'         then parse_unit_double
      when 'UnFl'         then parse_unit_float
      end

      return value
    end

    def parse_boolean;  @file.read_boolean; end
    def parse_double;   @file.read_double; end
    def parse_integer;  @file.read_int; end
    def parse_large_integer; @file.read_longlong; end
    def parse_identifier; @file.read_int; end
    def parse_index; @file.read_int; end
    def parse_offset; @file.read_int; end
    
    def parse_property
      {
        class: parse_class,
        id: parse_id
      }
    end

    def parse_enum
      {
        type: parse_id,
        value: parse_id
      }
    end

    def parse_enum_reference
      {
        class: parse_class,
        type: parse_id,
        value: parse_id
      }
    end

    def parse_alias
      len = @file.read_int
      @file.read_string len
    end

    def parse_file_path
      len = @file.read_int

      # Little-endian, because fuck the world.
      sig = @file.read_string(4)
      path_size = @file.read('l<')
      num_chars = @file.read('l<')

      path = @file.read_unicode_string(num_chars)

      {sig: sig, path: path}
    end

    def parse_list
      count = @file.read_int
      items = []

      count.times do |i|
        items << parse_item
      end

      return items
    end

    def parse_object_array
      raise NotImplementedError.new("Object array not implemented yet")
      count = @file.read_int
      items_in_obj = @file.read_int
      wat = @file.read_short
      
      puts count
      puts items_in_obj
      puts wat

      obj = {}
      count.times do |i|
        item = []
        name = @file.read_string(@file.read_int)
        puts name
        obj[name] = parse_list
      end

      return obj
    end

    def parse_raw_data
      len = @file.read_int
      @file.read(len)
    end

    def parse_reference
      num_items = @file.read_int
      items = []
      
      num_items.times do
        type = @file.read_string(4)
        PSD.logger.debug "Reference type = #{type}"

        value = case type
        when 'prop' then parse_property
        when 'Clss' then parse_class
        when 'Enmr' then parse_enum_reference
        when 'Idnt' then parse_identifier
        when 'indx' then parse_index
        when 'name' then @file.read_unicode_string
        when 'rele' then parse_offset
        end

        items << {type: type, value: value}
      end

      items
    end

    def parse_unit_double
      unit_id = @file.read_string(4)
      unit = case unit_id
      when '#Ang' then 'Angle'
      when '#Rsl' then 'Density'
      when '#Rlt' then 'Distance'
      when '#Nne' then 'None'
      when '#Prc' then 'Percent'
      when '#Pxl' then 'Pixels'
      when '#Mlm' then 'Millimeters'
      when '#Pnt' then 'Points'
      end

      value = @file.read_double
      {id: unit_id, unit: unit, value: value}
    end

    def parse_unit_float
      unit_id = @file.read_string(4)
      unit = case unit_id
      when '#Ang' then 'Angle'
      when '#Rsl' then 'Density'
      when '#Rlt' then 'Distance'
      when '#Nne' then 'None'
      when '#Prc' then 'Percent'
      when '#Pxl' then 'Pixels'
      when '#Mlm' then 'Millimeters'
      when '#Pnt' then 'Points'
      end

      value = @file.read_float
      {id: unit_id, unit: unit, value: value}
    end
  end
end