class FormatParser::DPXParser
  include FormatParser::IOUtils
  FILE_INFO = [
#    :x4,   # magic bytes SDPX, we read them anyway so not in the pattern
    :x4,   # u32  :image_offset,   :desc => 'Offset to image data in bytes', :req => true
    :x8,   # char :version, 8,     :desc => 'Version of header format', :req => true
    :x4,   # u32  :file_size,      :desc => "Total image size in bytes", :req => true
    :x4,   # u32  :ditto_key,      :desc => 'Whether the basic headers stay the same through the sequence (1 means they do)'
    :x4,   # u32  :generic_size,   :desc => 'Generic header length'
    :x4,   # u32  :industry_size,  :desc => 'Industry header length'
    :x4,   # u32  :user_size,      :desc => 'User header length'
    :x100, # char :filename, 100,  :desc => 'Original filename'
    :x24,  # char :timestamp, 24,  :desc => 'Creation timestamp'
    :x100, # char :creator, 100,   :desc => 'Creator application'
    :x200, # char :project, 200,   :desc => 'Project name'
    :x200, # char :copyright, 200, :desc => 'Copyright'
    :x4,   # u32  :encrypt_key,   :desc => 'Encryption key'
    :x104,  # blanking :reserve, 104
  ].join

  FILM_INFO = [
    :x2, # char :id, 2,          :desc => 'Film mfg. ID code (2 digits from film edge code)'
    :x2, # char :type, 2,        :desc => 'Film type (2 digits from film edge code)'
    :x2, # char :offset, 2,      :desc => 'Offset in perfs (2 digits from film edge code)'
    :x6, # char :prefix, 6,      :desc => 'Prefix (6 digits from film edge code'
    :x4, # char :count, 4,       :desc => 'Count (4 digits from film edge code)'
    :x32,# char :format, 32,     :desc => 'Format (e.g. Academy)'
    :x4, # u32 :frame_position,  :desc => 'Frame position in sequence'
    :x4, # u32 :sequence_extent, :desc => 'Sequence length'
    :x4, # u32 :held_count,      :desc => 'For how many frames the frame is held'
    :x4, # r32 :frame_rate,      :desc => 'Frame rate'
    :x4, # r32 :shutter_angle,   :desc => 'Shutter angle'
    :x4, # char :frame_id, 32,   :desc => 'Frame identification (keyframe)'
    :x4, # char :slate, 100,     :desc => 'Slate information'
    :x4, # blanking :reserve, 56
  ].join

  IMAGE_ELEMENT = [
    :x4, # u32 :data_sign, :desc => 'Data sign (0=unsigned, 1=signed). Core is unsigned', :req => true
    #
    :x4, # u32 :low_data,      :desc => 'Reference low data code value'
    :x4, # r32 :low_quantity,  :desc => 'Reference low quantity represented'
    :x4, # u32 :high_data,     :desc => 'Reference high data code value (1023 for 10bit per channel)'
    :x4, # r32 :high_quantity, :desc => 'Reference high quantity represented'
    #
    :x1, # u8 :descriptor,   :desc => 'Descriptor for this image element (ie Video or Film), by enum', :req => true
    # TODO - colirimetry information might be handy to recover,
    # as well as "bit size per element" (how many bits _per component_ we have) -
    # this will be different for, say, 8-bit DPX files versus 10-bit etc.
    :x1, # u8 :transfer,     :desc => 'Transfer function (ie Linear), by enum', :req => true
    :x1, # u8 :colorimetric, :desc => 'Colorimetric (ie YcbCr), by enum', :req => true
    :x1, # u8 :bit_size,     :desc => 'Bit size for element (ie 10)', :req => true
    #
    :x2, # u16 :packing,     :desc => 'Packing (0=Packed into 32-bit words, 1=Filled to 32-bit words))', :req => true
    :x2, # u16 :encoding,    :desc => "Encoding (0=None, 1=RLE)", :req => true
    :x4, # u32 :data_offset, :desc => 'Offset to data for this image element', :req => true
    :x4, # u32 :end_of_line_padding, :desc => "End-of-line padding for this image element"
    :x4, # u32 :end_of_image_padding, :desc => "End-of-line padding for this image element"
    :x32,# char :description, 32
  ].join

  IMAGE_INFO = [
    :x2, # u16 :orientation, OrientationInfo,    :desc => 'Orientation descriptor',    :req => true
    :n1, # u16 :number_elements,                   :desc => 'How many elements to scan', :req => true
    :N1, # u32 :pixels_per_line,                   :desc => 'Pixels per horizontal line', :req => true
    :N1, # u32 :lines_per_element,                 :desc => 'Line count', :req => true
    IMAGE_ELEMENT * 8, # 8 IMAGE_ELEMENT structures
    :x52, # blanking :reserve, 52
  ].join

  ORIENTATION_INFO = [
    :x4, #  u32 :x_offset
    :x4, #  u32 :y_offset
    #
    :x4, #  r32 :x_center
    :x4, #  r32 :y_center
    #
    :x4, #  u32 :x_size, :desc => 'Original X size'
    :x4, #  u32 :y_size, :desc => 'Original Y size'
    #
    :x100, #  char :filename, 100, :desc => "Source image filename"
    :x24,  #  char :timestamp, 24, :desc => "Source image/tape timestamp"
    :x32,  #  char :device,    32, :desc => "Input device or tape"
    :x32,  #  char :serial,    32, :desc => "Input device serial number"
    #
    :x4,   #  array :border, :u16, 4, :desc => 'Border validity: XL, XR, YT, YB'
    :x4,
    :x4,
    :x4,

    # TODO - the aspect ratio might be handy to recover since it
    # will be used in DPX files in, say, anamorphic (non-square pixels)
    :x4,   #  array :aspect_ratio , :u32, 2, :desc => "Aspect (H:V)"
    :x4,
    #
    :x28,  #  blanking :reserve, 28
  ].join

  DPX_INFO = [
    FILE_INFO,
    IMAGE_INFO,
    ORIENTATION_INFO,
  ].join

  DPX_INFO_LE = DPX_INFO.tr("n", "v").tr("N", "V")

  SIZEOF = ->(pattern) {
    bytes_per_element = {
      "v" => 2, # 16bit uints
      "n" => 2,
      "V" => 4, # 32bit uints
      "N" => 4,
      "C" => 1,
      "x" => 1,
    }
    pattern.scan(/[^\d]\d+/).map do |pattern|
      unpack_code = pattern[0]
      num_repetitions = pattern[1..-1].to_i
      bytes_per_element.fetch(unpack_code) * num_repetitions
    end.inject(&:+)
  }

  BE_MAGIC = 'SDPX'
  LE_MAGIC = BE_MAGIC.reverse
  HEADER_SIZE = SIZEOF[DPX_INFO] # Does not include the initial 4 bytes

  def information_from_io(io)
    io = FormatParser::IOConstraint.new(io)
    magic = io.read(4)

    return nil unless [BE_MAGIC, LE_MAGIC].include?(magic)

    unpack_pattern = DPX_INFO
    unpack_pattern = DPX_INFO_LE if magic == LE_MAGIC
    num_elements, pixels_per_line, num_lines, *_ = safe_read(io, HEADER_SIZE).unpack(unpack_pattern)
    FormatParser::FileInformation.image(
      file_type: :dpx,
      width_px: pixels_per_line,
      height_px: num_lines,
    )
  end

  FormatParser.register_parser_constructor self
end