import {AppSegmentParserBase} from '../parser.mjs'
import {segmentParsers} from '../plugins.mjs'


/*
http://fileformats.archiveteam.org/wiki/Photoshop_Image_Resources
In a TIFF file, tag 34377 contains Photoshop Image Resources.
In a JPEG file, an APP13 marker with an identifier of "Photoshop 3.0" contains Photoshop Image Resources.
Resource ID 0x0404 contains IPTC data.
Resource ID 0x040c may contain a thumbnail in JPEG/JFIF format.
Resource ID 0x040F contains an ICC profile.
Resource ID 0x0422 contains Exif data.
Resource ID 0x0424 contains XMP data.
*/

const HEADER_8    = 0x38
const HEADER_8BIM = 0x3842494D
const HEADER_IPTC = 0x0404
const MARKER = 0xED
const PHOTOSHOP = 'Photoshop'

export default class Iptc extends AppSegmentParserBase {

	static type = 'iptc'

	static translateValues = false
	static reviveValues = false

	// APP13 very complicated and doesn't just simply contain IPTC data.
	// It is in fact Photoshop format, which contains it's own chunks, each starting with 8BIM header.
	// IPTC is just one of those chunks and may start several hundreds of bytes into the segment.
	// IPTC chunk in the format starts with 8BIM followed by 4 4 (0x0404)
	// TLDR: We can't just compare bytes. The chunk has to be traversed.
	// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_38034

	static canHandle(file, offset, length) {
		let isApp13 = file.getUint8(offset + 1) === MARKER
				   && file.getString(offset + 4, 9) === PHOTOSHOP
		if (!isApp13) return false
		let i = this.containsIptc8bim(file, offset, length)
		return i !== undefined
	}

	// WARNING: There can be files with APP13 segment, containing various Photoshop related
	// data, but no IPTC. We musn't falsely accept these segments as IPTC!
	static headerLength(chunk, offset, length) {
		let nameHeaderLength
		let i = this.containsIptc8bim(chunk, offset, length)
		if (i !== undefined) {
			// Get the length of the name header (which is padded to an even number of bytes)
			nameHeaderLength = chunk.getUint8(offset + i + 7)
			if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1
			// Check for pre photoshop 6 format
			if (nameHeaderLength === 0) nameHeaderLength = 4
			return i + 8 + nameHeaderLength
		}
	}

	static containsIptc8bim(chunk, offset, length) {
		for (let i = 0; i < length; i++)
			if (this.isIptcSegmentHead(chunk, offset + i))
				return i
	}

	static isIptcSegmentHead(chunk, offset) {
		// isIptcSegmentHead is called on each byte while traversing the file.
		// This could be hundreds of times. We don't want to read the same overlaping range
		// over and over, so just read the first byte and only then continue.
		return chunk.getUint8(offset)      === HEADER_8    // 8 - photoshop segment start
			&& chunk.getUint32(offset)     === HEADER_8BIM // 8BIM - photoshop segment start
			&& chunk.getUint16(offset + 4) === HEADER_IPTC // IPTC segment head
	}

	parse() {
		let {raw} = this
		let iterableLength = this.chunk.byteLength - 1
		let foundFirstProp = false
		for (let offset = 0; offset < iterableLength; offset++) {
			// NOTE: IPTC has wariable header. data can start immediately or after couple of bytes. So we need to seek
			// through the data until we find the first 1C02 marker.
			// reading Uint8 and then another to prevent unnecessarry read of two subsequent bytes, when iterating
			if (this.chunk.getUint8(offset) === 0x1C && this.chunk.getUint8(offset + 1) === 0x02) {
                foundFirstProp = true
				let size = this.chunk.getUint16(offset + 3)
				let key = this.chunk.getUint8(offset + 2)
				let val = this.chunk.getLatin1String(offset + 5, size)
				raw.set(key, this.pluralizeValue(raw.get(key), val))
				// skip iterating over the bytes we've already read
				offset += 4 + size
			} else if (foundFirstProp) {
				// We would infinately loop through the rest of the chunk (due to the dynamic header length).
				break
			}
		}
		this.translate()
		return this.output
	}

	pluralizeValue(existingVal, newVal) {
		if (existingVal !== undefined) {
			if (existingVal instanceof Array) {
				existingVal.push(newVal)
				return existingVal
			} else {
				return [existingVal, newVal]
			}
		} else {
			return newVal
		}
	}

}

segmentParsers.set('iptc', Iptc)