/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.sephiroth.android.library.exif2

import android.util.Log
import it.sephiroth.android.library.exif2.utils.CountedDataInputStream
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.charset.Charset
import java.util.*
import kotlin.math.min

internal open class ExifParser
@Throws(IOException::class, ExifInvalidFormatException::class)
private constructor(
	inputStream : InputStream,
	private val mOptions : Int,
	private val mInterface : ExifInterface
) {
	
	// number of tags in the current IFD area.
	private var tagCountInCurrentIfd = 0
	
	/**
	 * the ID of current IFD.
	 *
	 * @see IfdData.TYPE_IFD_0
	 * @see IfdData.TYPE_IFD_1
	 * @see IfdData.TYPE_IFD_GPS
	 * @see IfdData.TYPE_IFD_INTEROPERABILITY
	 * @see IfdData.TYPE_IFD_EXIF
	 */
	var currentIfd : Int = 0
		private set
	
	/**
	 * If [.next] return [.EVENT_NEW_TAG] or
	 * [.EVENT_VALUE_OF_REGISTERED_TAG], call this function to get the
	 * corresponding tag.
	 *
	 *
	 * For [.EVENT_NEW_TAG], the tag may not contain the value if the size
	 * of the value is greater than 4 bytes. One should call
	 * [ExifTag.hasValue] to check if the tag contains value. If there
	 * is no value,call [.registerForTagValue] to have the parser
	 * emit [.EVENT_VALUE_OF_REGISTERED_TAG] when it reaches the area
	 * pointed by the offset.
	 *
	 *
	 * When [.EVENT_VALUE_OF_REGISTERED_TAG] is emitted, the value of the
	 * tag will have already been read except for tags of undefined type. For
	 * tags of undefined type, call one of the read methods to get the value.
	 *
	 * @see .registerForTagValue
	 * @see .read
	 * @see .read
	 * @see .readLong
	 * @see .readRational
	 * @see .readString
	 * @see .readString
	 */
	var tag : ExifTag? = null
		private set
	
	private var mImageEvent : ImageEvent? = null
	private var mStripSizeTag : ExifTag? = null
	private var mJpegSizeTag : ExifTag? = null
	private var mNeedToParseOffsetsInCurrentIfd : Boolean = false
	private var mDataAboveIfd0 : ByteArray? = null
	private var mIfd0Position : Int = 0
	
	var qualityGuess : Int = 0
		private set
	var imageWidth : Int = 0
		private set
	var imageLength : Int = 0
		private set
	var jpegProcess : Short = 0
		private set
	
	var uncompressedDataPosition = 0
		private set
	
	private val mByteArray = ByteArray(8)
	private val mByteBuffer = ByteBuffer.wrap(mByteArray)
	
	private val isThumbnailRequested : Boolean
		get() = mOptions and ExifInterface.Options.OPTION_THUMBNAIL != 0
	
	/**
	 * When receiving [.EVENT_UNCOMPRESSED_STRIP], call this function to
	 * get the index of this strip.
	 */
	val stripIndex : Int
		get() = mImageEvent?.stripIndex ?: 0
	
	/**
	 * When receiving [.EVENT_UNCOMPRESSED_STRIP], call this function to
	 * get the strip size.
	 */
	val stripSize : Int
		get() = mStripSizeTag?.getValueAt(0)?.toInt() ?: 0
	
	/**
	 * When receiving [.EVENT_COMPRESSED_IMAGE], call this function to get
	 * the image data size.
	 */
	val compressedImageSize : Int
		get() = mJpegSizeTag?.getValueAt(0)?.toInt() ?: 0
	
	/**
	 * Gets the byte order of the current InputStream.
	 */
	val byteOrder : ByteOrder
		get() = mTiffStream.byteOrder
	
	val sections : List<Section>
		get() = mSections
	
	private val mCorrespondingEvent = TreeMap<Int, Any>()
	
	private val mSections = ArrayList<Section>(0)
	
	private var mIfdStartOffset = 0
	
	private val mTiffStream : CountedDataInputStream = seekTiffData(inputStream)
	
	init {
		
		// Log.d( TAG, "sections size: " + mSections.size() );
		
		val tiffStream = mTiffStream
		
		parseTiffHeader(tiffStream)
		
		val offset = tiffStream.readUnsignedInt()
		if(offset > Integer.MAX_VALUE) {
			throw ExifInvalidFormatException("Invalid offset $offset")
		}
		mIfd0Position = offset.toInt()
		currentIfd = IfdData.TYPE_IFD_0
		
		if(isIfdRequested(IfdData.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) {
			registerIfd(IfdData.TYPE_IFD_0, offset)
			if(offset != DEFAULT_IFD0_OFFSET.toLong()) {
				val ba = ByteArray(offset.toInt() - DEFAULT_IFD0_OFFSET)
				mDataAboveIfd0 = ba
				read(ba)
			}
		}
	}
	
	private fun readInt(b : ByteArray, @Suppress("SameParameterValue") offset : Int) : Int {
		mByteBuffer.rewind()
		mByteBuffer.put(b, offset, 4)
		mByteBuffer.rewind()
		return mByteBuffer.int
	}
	
	private fun readShort(b : ByteArray, @Suppress("SameParameterValue") offset : Int) : Short {
		mByteBuffer.rewind()
		mByteBuffer.put(b, offset, 2)
		mByteBuffer.rewind()
		return mByteBuffer.short
	}
	
	@Throws(IOException::class, ExifInvalidFormatException::class)
	private fun seekTiffData(inputStream : InputStream) : CountedDataInputStream {
		val dataStream =
			CountedDataInputStream(inputStream)
		var tiffStream : CountedDataInputStream? = null
		
		var a = dataStream.readUnsignedByte()
		val b = dataStream.readUnsignedByte()
		
		
		if(a == 137 && b == 80) error("maybe PNG image")
		
		if(a != 0xFF || b != JpegHeader.TAG_SOI) error("invalid jpeg header")
		
		while(true) {
			val itemlen : Int
			var marker : Int
			
			val got : Int
			val data : ByteArray
			
			var prev = 0
			a = 0
			while(true) {
				marker = dataStream.readUnsignedByte()
				if(marker != 0xff && prev == 0xff) break
				prev = marker
				a ++
			}
			
			if(a > 10) {
				Log.w(TAG, "Extraneous ${a - 1} padding bytes before section $marker")
			}
			
			// Read the length of the section.
			val lh = dataStream.readByte().toInt()
			val ll = dataStream.readByte().toInt()
			itemlen = lh and 0xff shl 8 or (ll and 0xff)
			
			if(itemlen < 2) {
				throw ExifInvalidFormatException("Invalid marker")
			}
			
			data = ByteArray(itemlen)
			data[0] = lh.toByte()
			data[1] = ll.toByte()
			
			// Log.i( TAG, "marker: " + String.format( "0x%2X", marker ) + ": " + itemlen + ", position: " + dataStream.getReadByteCount() + ", available: " + dataStream.available() );
			// got = dataStream.read( data, 2, itemlen-2 );
			
			got = readBytes(dataStream, data, 2, itemlen - 2)
			
			if(got != itemlen - 2) {
				throw ExifInvalidFormatException("Premature end of file? Expecting " + (itemlen - 2) + ", received " + got)
			}
			
			val section = Section(type = marker, size = itemlen, data = data)
			
			var ignore = false
			
			when(marker) {
				JpegHeader.TAG_M_SOS -> {
					// stop before hitting compressed data
					mSections.add(section)
					uncompressedDataPosition = dataStream.readByteCount
					return tiffStream ?: error("stop before hitting compressed data")
				}
				
				JpegHeader.TAG_M_DQT ->
					// Use for jpeg quality guessing
					process_M_DQT(data)
				
				JpegHeader.TAG_M_DHT -> {
				}
				
				// in case it's a tables-only JPEG stream
				JpegHeader.TAG_M_EOI -> {
					error("\"No image in jpeg!\"")
				}
				
				JpegHeader.TAG_M_COM ->
					// Comment section
					ignore = true
				
				JpegHeader.TAG_M_JFIF -> if(itemlen < 16) {
					ignore = true
				}
				
				JpegHeader.TAG_M_IPTC -> {
				}
				
				JpegHeader.TAG_M_SOF0, JpegHeader.TAG_M_SOF1, JpegHeader.TAG_M_SOF2, JpegHeader.TAG_M_SOF3, JpegHeader.TAG_M_SOF5, JpegHeader.TAG_M_SOF6, JpegHeader.TAG_M_SOF7, JpegHeader.TAG_M_SOF9, JpegHeader.TAG_M_SOF10, JpegHeader.TAG_M_SOF11, JpegHeader.TAG_M_SOF13, JpegHeader.TAG_M_SOF14, JpegHeader.TAG_M_SOF15 -> process_M_SOFn(
					data,
					marker
				)
				
				JpegHeader.TAG_M_EXIF -> if(itemlen >= 8) {
					val header = readInt(data, 2)
					val headerTail = readShort(data, 6)
					// header = Exif, headerTail=\0\0
					if(header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
						tiffStream =
							CountedDataInputStream(
								ByteArrayInputStream(data, 8, itemlen - 8)
							)
						tiffStream.end = itemlen - 6
						ignore = false
					} else {
						Log.v(TAG, "Image cotains XMP section")
					}
				}
				
				else -> Log.w(
					TAG,
					"Unknown marker: " + String.format("0x%2X", marker) + ", length: " + itemlen
				)
			}
			
			if(! ignore) {
				// Log.d( TAG, "adding section with size: " + section.size );
				mSections.add(section)
			} else {
				Log.v(
					TAG,
					"ignoring marker: " + String.format("0x%2X", marker) + ", length: " + itemlen
				)
			}
		}
	}
	
	/**
	 * Using this instead of the default [java.io.InputStream.read] because
	 * on remote input streams reading large amount of data can fail
	 *
	 * @param dataStream
	 * @param data
	 * @param offsetArg
	 * @param length
	 * @return
	 * @throws IOException
	 */
	@Throws(IOException::class)
	private fun readBytes(
		dataStream : InputStream,
		data : ByteArray,
		@Suppress("SameParameterValue") offsetArg : Int,
		length : Int
	) : Int {
		var offset = offsetArg
		var count = 0
		var n : Int
		var max_length = min(1024, length)
		while(true) {
			n = dataStream.read(data, offset, max_length)
			if(n <= 0) break
			count += n
			offset += n
			max_length = min(max_length, length - count)
		}
		return count
	}
	
	private fun process_M_SOFn(data : ByteArray, marker : Int) {
		if(data.size > 7) {
			//int data_precision = data[2] & 0xff;
			//int num_components = data[7] & 0xff;
			imageLength = Get16m(data, 3)
			imageWidth = Get16m(data, 5)
		}
		jpegProcess = marker.toShort()
	}
	
	private fun process_M_DQT(data : ByteArray) {
		var a = 2
		var c : Int
		var tableindex : Int
		var coefindex : Int
		var cumsf = 0.0
		var reftable : IntArray? = null
		var allones = 1
		
		while(a < data.size) {
			c = data[a ++].toInt()
			tableindex = c and 0x0f
			
			if(tableindex < 2) {
				reftable = deftabs[tableindex]
			}
			
			// Read in the table, compute statistics relative to reference table
			coefindex = 0
			while(coefindex < 64) {
				val `val` : Int
				if(c shr 4 != 0) {
					var temp : Int
					temp = data[a ++].toInt()
					temp *= 256
					`val` = data[a ++].toInt() + temp
				} else {
					`val` = data[a ++].toInt()
				}
				if(reftable != null) {
					val x : Double
					// scaling factor in percent
					x = 100.0 * `val`.toDouble() / reftable[coefindex].toDouble()
					cumsf += x
					// separate check for all-ones table (Q 100)
					if(`val` != 1) allones = 0
				}
				coefindex ++
			}
			// Print summary stats
			if(reftable != null) { // terse output includes quality
				val qual : Double
				cumsf /= 64.0    // mean scale factor
				
				qual = when {
					allones != 0 -> 100.0 // special case for all-ones table
					cumsf <= 100.0 -> (200.0 - cumsf) / 2.0
					else -> 5000.0 / cumsf
				}
				
				if(tableindex == 0) {
					qualityGuess = (qual + 0.5).toInt()
					// Log.v( TAG, "quality guess: " + mQualityGuess );
				}
			}
		}
	}
	
	@Throws(IOException::class, ExifInvalidFormatException::class)
	private fun parseTiffHeader(stream : CountedDataInputStream) {
		
		stream.byteOrder = when(stream.readShort()) {
			LITTLE_ENDIAN_TAG -> ByteOrder.LITTLE_ENDIAN
			BIG_ENDIAN_TAG -> ByteOrder.BIG_ENDIAN
			else -> throw ExifInvalidFormatException("Invalid TIFF header")
		}
		
		if(stream.readShort() != TIFF_HEADER_TAIL) {
			throw ExifInvalidFormatException("Invalid TIFF header")
		}
	}
	
	private fun isIfdRequested(ifdType : Int) : Boolean {
		when(ifdType) {
			IfdData.TYPE_IFD_0 -> return mOptions and ExifInterface.Options.OPTION_IFD_0 != 0
			IfdData.TYPE_IFD_1 -> return mOptions and ExifInterface.Options.OPTION_IFD_1 != 0
			IfdData.TYPE_IFD_EXIF -> return mOptions and ExifInterface.Options.OPTION_IFD_EXIF != 0
			IfdData.TYPE_IFD_GPS -> return mOptions and ExifInterface.Options.OPTION_IFD_GPS != 0
			IfdData.TYPE_IFD_INTEROPERABILITY -> return mOptions and ExifInterface.Options.OPTION_IFD_INTEROPERABILITY != 0
		}
		return false
	}
	
	private fun needToParseOffsetsInCurrentIfd() : Boolean {
		return when(currentIfd) {
			
			IfdData.TYPE_IFD_0 ->
				isIfdRequested(IfdData.TYPE_IFD_EXIF) ||
					isIfdRequested(IfdData.TYPE_IFD_GPS) ||
					isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY) ||
					isIfdRequested(IfdData.TYPE_IFD_1)
			
			IfdData.TYPE_IFD_1 -> isThumbnailRequested
			
			IfdData.TYPE_IFD_EXIF ->
				// The offset to interoperability IFD is located in Exif IFD
				isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)
			
			else -> false
		}
	}
	
	private fun registerIfd(ifdType : Int, offset : Long) {
		// Cast unsigned int to int since the offset is always smaller
		// than the size of M_EXIF (65536)
		mCorrespondingEvent[offset.toInt()] = IfdEvent(ifdType, isIfdRequested(ifdType))
	}
	
	/**
	 * Equivalent to read(buffer, 0, buffer.length).
	 */
	@Throws(IOException::class)
	fun read(buffer : ByteArray) : Int = mTiffStream.read(buffer)
	
	//
	//	/**
	//	 * Parses the the given InputStream with default options; that is, every IFD
	//	 * and thumbnaill will be parsed.
	//	 *
	//	 * @throws java.io.IOException
	//	 * @throws ExifInvalidFormatException
	//	 */
	//	protected static ExifParser parse( InputStream inputStream, boolean requestThumbnail, ExifInterface iRef ) throws IOException, ExifInvalidFormatException {
	//		return new ExifParser( inputStream, OPTION_IFD_0 | OPTION_IFD_1 | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY | ( requestThumbnail ? OPTION_THUMBNAIL : 0 ), iRef );
	//	}
	
	/**
	 * Moves the parser forward and returns the next parsing event
	 *
	 * @throws java.io.IOException
	 * @throws ExifInvalidFormatException
	 * @see .EVENT_START_OF_IFD
	 * @see .EVENT_NEW_TAG
	 * @see .EVENT_VALUE_OF_REGISTERED_TAG
	 * @see .EVENT_COMPRESSED_IMAGE
	 * @see .EVENT_UNCOMPRESSED_STRIP
	 * @see .EVENT_END
	 */
	@Throws(IOException::class, ExifInvalidFormatException::class)
	operator fun next() : Int {
		
		val offset = mTiffStream.readByteCount
		val endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * tagCountInCurrentIfd
		if(offset < endOfTags) {
			val tag = readTag()
			this.tag = tag
			if(tag == null) {
				return next()
			} else if(mNeedToParseOffsetsInCurrentIfd) {
				checkOffsetOrImageTag(tag)
			}
			return EVENT_NEW_TAG
		} else if(offset == endOfTags) {
			// There is a link to ifd1 at the end of ifd0
			if(currentIfd == IfdData.TYPE_IFD_0) {
				val ifdOffset = readUnsignedLong()
				if(isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested) {
					if(ifdOffset != 0L) {
						registerIfd(IfdData.TYPE_IFD_1, ifdOffset)
					}
				}
			} else {
				var offsetSize = 4
				// Some camera models use invalid length of the offset
				if(mCorrespondingEvent.size > 0) {
					val firstEntry = mCorrespondingEvent.firstEntry() !!
					offsetSize = firstEntry.key - mTiffStream.readByteCount
				}
				if(offsetSize < 4) {
					Log.w(TAG, "Invalid size of link to next IFD: $offsetSize")
				} else {
					val ifdOffset = readUnsignedLong()
					if(ifdOffset != 0L) {
						Log.w(TAG, "Invalid link to next IFD: $ifdOffset")
					}
				}
			}
		}
		while(mCorrespondingEvent.size != 0) {
			val entry = mCorrespondingEvent.pollFirstEntry() !!
			val event = entry.value
			try {
				// Log.v(TAG, "skipTo: " + entry.getKey());
				skipTo(entry.key)
			} catch(e : IOException) {
				Log.w(
					TAG,
					"Failed to skip to data at: ${entry.key} for ${event.javaClass.name}, the file may be broken."
				)
				continue
			}
			
			if(event is IfdEvent) {
				currentIfd = event.ifd
				tagCountInCurrentIfd = mTiffStream.readUnsignedShort()
				mIfdStartOffset = entry.key
				
				if(tagCountInCurrentIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mTiffStream.end) {
					Log.w(TAG, "Invalid size of IFD $currentIfd")
					return EVENT_END
				}
				
				mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd()
				if(event.isRequested) {
					return EVENT_START_OF_IFD
				} else {
					skipRemainingTagsInCurrentIfd()
				}
			} else if(event is ImageEvent) {
				mImageEvent = event
				return event.type
			} else {
				val tagEvent = event as ExifTagEvent
				val tag = tagEvent.tag
				this.tag = tag
				if(tag.dataType != ExifTag.TYPE_UNDEFINED) {
					readFullTagValue(tag)
					checkOffsetOrImageTag(tag)
				}
				if(tagEvent.isRequested) {
					return EVENT_VALUE_OF_REGISTERED_TAG
				}
			}
		}
		return EVENT_END
	}
	
	/**
	 * Skips the tags area of current IFD, if the parser is not in the tag area,
	 * nothing will happen.
	 *
	 * @throws java.io.IOException
	 * @throws ExifInvalidFormatException
	 */
	@Throws(IOException::class, ExifInvalidFormatException::class)
	protected fun skipRemainingTagsInCurrentIfd() {
		
		val endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * tagCountInCurrentIfd
		var offset = mTiffStream.readByteCount
		if(offset > endOfTags) return
		
		if(mNeedToParseOffsetsInCurrentIfd) {
			while(offset < endOfTags) {
				val tag = readTag()
				this.tag = tag
				offset += TAG_SIZE
				if(tag == null) {
					continue
				}
				checkOffsetOrImageTag(tag)
			}
		} else {
			skipTo(endOfTags)
		}
		val ifdOffset = readUnsignedLong()
		// For ifd0, there is a link to ifd1 in the end of all tags
		if(currentIfd == IfdData.TYPE_IFD_0 && (isIfdRequested(IfdData.TYPE_IFD_1) || isThumbnailRequested)) {
			if(ifdOffset > 0) {
				registerIfd(IfdData.TYPE_IFD_1, ifdOffset)
			}
		}
	}
	
	@Throws(IOException::class)
	private fun skipTo(offset : Int) {
		mTiffStream.skipTo(offset.toLong())
		
		// Log.v(TAG, "available: " + mTiffStream.available() );
		while(! mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
			mCorrespondingEvent.pollFirstEntry()
		}
	}
	
	/**
	 * When getting [.EVENT_NEW_TAG] in the tag area of IFD, the tag may
	 * not contain the value if the size of the value is greater than 4 bytes.
	 * When the value is not available here, call this method so that the parser
	 * will emit [.EVENT_VALUE_OF_REGISTERED_TAG] when it reaches the area
	 * where the value is located.
	 *
	 * @see .EVENT_VALUE_OF_REGISTERED_TAG
	 */
	fun registerForTagValue(tag : ExifTag) {
		if(tag.offset >= mTiffStream.readByteCount) {
			mCorrespondingEvent[tag.offset] = ExifTagEvent(tag, true)
		}
	}
	
	private fun registerCompressedImage(offset : Long) {
		mCorrespondingEvent[offset.toInt()] = ImageEvent(EVENT_COMPRESSED_IMAGE)
	}
	
	private fun registerUncompressedStrip(stripIndex : Int, offset : Long) {
		mCorrespondingEvent[offset.toInt()] = ImageEvent(EVENT_UNCOMPRESSED_STRIP, stripIndex)
	}
	
	@Throws(IOException::class, ExifInvalidFormatException::class)
	private fun readTag() : ExifTag? {
		
		val tagId = mTiffStream.readShort()
		val dataFormat = mTiffStream.readShort()
		val numOfComp = mTiffStream.readUnsignedInt()
		if(numOfComp > Integer.MAX_VALUE) {
			throw ExifInvalidFormatException("Number of component is larger then Integer.MAX_VALUE")
		}
		// Some invalid image file contains invalid data type. Ignore those tags
		if(! ExifTag.isValidType(dataFormat)) {
			Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat))
			mTiffStream.skip(4)
			return null
		}
		// TODO: handle numOfComp overflow
		val tag = ExifTag(
			tagId,
			dataFormat,
			numOfComp.toInt(),
			currentIfd,
			numOfComp.toInt() != ExifTag.SIZE_UNDEFINED
		)
		val dataSize = tag.dataSize
		if(dataSize > 4) {
			val offset = mTiffStream.readUnsignedInt()
			if(offset > Integer.MAX_VALUE) {
				throw ExifInvalidFormatException("offset is larger then Integer.MAX_VALUE")
			}
			// Some invalid images put some undefined data before IFD0.
			// Read the data here.
			if(offset < mIfd0Position && dataFormat == ExifTag.TYPE_UNDEFINED) {
				val buf = ByteArray(numOfComp.toInt())
				System.arraycopy(
					mDataAboveIfd0 !!,
					offset.toInt() - DEFAULT_IFD0_OFFSET,
					buf,
					0,
					numOfComp.toInt()
				)
				tag.setValue(buf)
			} else {
				tag.offset = offset.toInt()
			}
		} else {
			val defCount = tag.hasDefinedCount
			// Set defined count to 0 so we can add \0 to non-terminated strings
			tag.hasDefinedCount = false
			// Read value
			readFullTagValue(tag)
			tag.hasDefinedCount = defCount
			mTiffStream.skip((4 - dataSize).toLong())
			// Set the offset to the position of value.
			tag.offset = mTiffStream.readByteCount - 4
		}
		return tag
	}
	
	/**
	 * Check the tag, if the tag is one of the offset tag that points to the IFD
	 * or image the caller is interested in, register the IFD or image.
	 */
	private fun checkOffsetOrImageTag(tag : ExifTag) {
		// Some invalid formattd image contains tag with 0 size.
		if(tag.componentCount == 0) {
			return
		}
		val tid = tag.tagId
		val ifd = tag.ifd
		if(tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
			if(isIfdRequested(IfdData.TYPE_IFD_EXIF) || isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) {
				registerIfd(IfdData.TYPE_IFD_EXIF, tag.getValueAt(0))
			}
		} else if(tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
			if(isIfdRequested(IfdData.TYPE_IFD_GPS)) {
				registerIfd(IfdData.TYPE_IFD_GPS, tag.getValueAt(0))
			}
		} else if(tid == TAG_INTEROPERABILITY_IFD && checkAllowed(
				ifd,
				ExifInterface.TAG_INTEROPERABILITY_IFD
			)) {
			if(isIfdRequested(IfdData.TYPE_IFD_INTEROPERABILITY)) {
				registerIfd(IfdData.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0))
			}
		} else if(tid == TAG_JPEG_INTERCHANGE_FORMAT && checkAllowed(
				ifd,
				ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT
			)) {
			if(isThumbnailRequested) {
				registerCompressedImage(tag.getValueAt(0))
			}
		} else if(tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH && checkAllowed(
				ifd,
				ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
			)) {
			if(isThumbnailRequested) {
				mJpegSizeTag = tag
			}
		} else if(tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
			if(isThumbnailRequested) {
				if(tag.hasValue) {
					for(i in 0 until tag.componentCount) {
						if(tag.dataType == ExifTag.TYPE_UNSIGNED_SHORT) {
							registerUncompressedStrip(i, tag.getValueAt(i))
						} else {
							registerUncompressedStrip(i, tag.getValueAt(i))
						}
					}
				} else {
					mCorrespondingEvent[tag.offset] = ExifTagEvent(tag, false)
				}
			}
		} else if(tid == TAG_STRIP_BYTE_COUNTS && checkAllowed(
				ifd,
				ExifInterface.TAG_STRIP_BYTE_COUNTS
			) && isThumbnailRequested && tag.hasValue) {
			mStripSizeTag = tag
		}
	}
	
	fun isDefinedTag(ifdId : Int, tagId : Short) : Boolean {
		return mInterface.tagInfo.get(
			ExifInterface.defineTag(
				ifdId,
				tagId
			)
		) != ExifInterface.DEFINITION_NULL
	}
	
	private fun checkAllowed(ifd : Int, tagId : Int) : Boolean {
		val info = mInterface.tagInfo.get(tagId)
		return if(info == ExifInterface.DEFINITION_NULL) {
			false
		} else ExifInterface.isIfdAllowed(info, ifd)
	}
	
	@Throws(IOException::class)
	fun readFullTagValue(tag : ExifTag) {
		
		// Some invalid images contains tags with wrong size, check it here
		val type = tag.dataType
		val componentCount = tag.componentCount
		
		// sanity check
		if(componentCount >= 0x66000000) throw IOException("size out of bounds")
		
		if(type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
			type == ExifTag.TYPE_UNSIGNED_BYTE) {
			var size = tag.componentCount
			if(mCorrespondingEvent.size > 0) {
				val firstEntry = mCorrespondingEvent.firstEntry() !!
				if(firstEntry.key < mTiffStream.readByteCount + size) {
					val event = firstEntry.value
					if(event is ImageEvent) {
						// Tag value overlaps thumbnail, ignore thumbnail.
						Log.w(TAG, "Thumbnail overlaps value for tag: \n$tag")
						val entry = mCorrespondingEvent.pollFirstEntry() !!
						Log.w(TAG, "Invalid thumbnail offset: " + entry.key)
					} else {
						// Tag value overlaps another tag, shorten count
						if(event is IfdEvent) {
							Log.w(TAG, "Ifd ${event.ifd} overlaps value for tag: \n$tag")
						} else if(event is ExifTagEvent) {
							Log.w(
								TAG,
								"Tag value for tag: \n${event.tag} overlaps value for tag: \n$tag"
							)
						}
						size = firstEntry.key - mTiffStream.readByteCount
						Log.w(TAG, "Invalid size of tag: \n$tag setting count to: $size")
						tag.forceSetComponentCount(size)
					}
				}
			}
		}
		when(tag.dataType) {
			ExifTag.TYPE_UNSIGNED_BYTE, ExifTag.TYPE_UNDEFINED ->
				tag.setValue(ByteArray(componentCount).also { read(it) })
			
			ExifTag.TYPE_ASCII ->
				tag.setValue(readString(componentCount))
			
			ExifTag.TYPE_UNSIGNED_SHORT ->
				tag.setValue(IntArray(componentCount) { readUnsignedShort() })
			
			ExifTag.TYPE_LONG ->
				tag.setValue(IntArray(componentCount) { readLong().toInt() })
			ExifTag.TYPE_UNSIGNED_LONG ->
				tag.setValue(LongArray(componentCount) { readUnsignedLong() })
			
			ExifTag.TYPE_RATIONAL ->
				tag.setValue(Array(componentCount) { readRational() })
			ExifTag.TYPE_UNSIGNED_RATIONAL ->
				tag.setValue(Array(componentCount) { readUnsignedRational() })
			
		}
		
		// Log.v( TAG, "\n" + tag.toString() );
	}
	
	/**
	 * Reads bytes from the InputStream.
	 */
	@Throws(IOException::class)
	protected fun read(buffer : ByteArray, offset : Int, length : Int) : Int =
		mTiffStream.read(buffer, offset, length)
	
	/**
	 * Reads a String from the InputStream with US-ASCII charset. The parser
	 * will read n bytes and convert it to ascii string. This is used for
	 * reading values of type [ExifTag.TYPE_ASCII].
	 */
	
	/**
	 * Reads a String from the InputStream with the given charset. The parser
	 * will read n bytes and convert it to string. This is used for reading
	 * values of type [ExifTag.TYPE_ASCII].
	 */
	@Throws(IOException::class)
	@JvmOverloads
	protected fun readString(n : Int, charset : Charset = US_ASCII) : String =
		when {
			n <= 0 -> ""
			else -> mTiffStream.readString(n, charset)
		}
	
	/**
	 * Reads value of type [ExifTag.TYPE_UNSIGNED_SHORT] from the
	 * InputStream.
	 */
	@Throws(IOException::class)
	protected fun readUnsignedShort() : Int =
		mTiffStream.readShort().toInt() and 0xffff
	
	/**
	 * Reads value of type [ExifTag.TYPE_UNSIGNED_LONG] from the
	 * InputStream.
	 */
	@Throws(IOException::class)
	protected fun readUnsignedLong() : Long {
		return readLong() and 0xffffffffL
	}
	
	/**
	 * Reads value of type [ExifTag.TYPE_UNSIGNED_RATIONAL] from the
	 * InputStream.
	 */
	@Throws(IOException::class)
	protected fun readUnsignedRational() : Rational {
		val nomi = readUnsignedLong()
		val denomi = readUnsignedLong()
		return Rational(nomi, denomi)
	}
	
	/**
	 * Reads value of type [ExifTag.TYPE_LONG] from the InputStream.
	 */
	@Throws(IOException::class)
	protected fun readLong() : Long =
		mTiffStream.readInt().toLong()
	
	/**
	 * Reads value of type [ExifTag.TYPE_RATIONAL] from the InputStream.
	 */
	@Throws(IOException::class)
	protected fun readRational() : Rational {
		val nomi = readLong()
		val denomi = readLong()
		return Rational(nomi, denomi)
	}
	
	private class ImageEvent {
		internal var stripIndex : Int = 0
		internal var type : Int = 0
		
		internal constructor(type : Int) {
			this.stripIndex = 0
			this.type = type
		}
		
		internal constructor(type : Int, stripIndex : Int) {
			this.type = type
			this.stripIndex = stripIndex
		}
	}
	
	private class IfdEvent internal constructor(
		internal var ifd : Int,
		internal var isRequested : Boolean
	)
	
	private class ExifTagEvent internal constructor(
		internal var tag : ExifTag,
		internal var isRequested : Boolean
	)
	
	class Section(var size : Int, var type : Int, var data : ByteArray)

	companion object {
		private const val TAG = "ExifParser"
		
		/**
		 * When the parser reaches a new IFD area. Call [.getCurrentIfd] to
		 * know which IFD we are in.
		 */
		const val EVENT_START_OF_IFD = 0
		/**
		 * When the parser reaches a new tag. Call [.getTag]to get the
		 * corresponding tag.
		 */
		const val EVENT_NEW_TAG = 1
		/**
		 * When the parser reaches the value area of tag that is registered by
		 * [.registerForTagValue] previously. Call [.getTag]
		 * to get the corresponding tag.
		 */
		const val EVENT_VALUE_OF_REGISTERED_TAG = 2
		/**
		 * When the parser reaches the compressed image area.
		 */
		const val EVENT_COMPRESSED_IMAGE = 3
		/**
		 * When the parser reaches the uncompressed image strip. Call
		 * [.getStripIndex] to get the index of the strip.
		 *
		 * @see .getStripIndex
		 */
		const val EVENT_UNCOMPRESSED_STRIP = 4
		/**
		 * When there is nothing more to parse.
		 */
		const val EVENT_END = 5
		
		protected const val EXIF_HEADER = 0x45786966 // EXIF header "Exif"
		protected const val EXIF_HEADER_TAIL = 0x0000.toShort() // EXIF header in M_EXIF
		// TIFF header
		protected const val LITTLE_ENDIAN_TAG = 0x4949.toShort() // "II"
		protected const val BIG_ENDIAN_TAG = 0x4d4d.toShort() // "MM"
		protected const val TIFF_HEADER_TAIL : Short = 0x002A
		protected const val TAG_SIZE = 12
		protected const val OFFSET_SIZE = 2
		protected const val DEFAULT_IFD0_OFFSET = 8
		private val US_ASCII = Charset.forName("US-ASCII")
		private val TAG_EXIF_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)
		private val TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)
		private val TAG_INTEROPERABILITY_IFD =
			ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)
		private val TAG_JPEG_INTERCHANGE_FORMAT =
			ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)
		private val TAG_JPEG_INTERCHANGE_FORMAT_LENGTH =
			ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)
		private val TAG_STRIP_OFFSETS = ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)
		private val TAG_STRIP_BYTE_COUNTS =
			ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)
		
		private val std_luminance_quant_tbl : IntArray
		private val std_chrominance_quant_tbl : IntArray
		val deftabs : Array<IntArray>
		
		init {
			std_luminance_quant_tbl = intArrayOf(
				16,
				11,
				12,
				14,
				12,
				10,
				16,
				14,
				13,
				14,
				18,
				17,
				16,
				19,
				24,
				40,
				26,
				24,
				22,
				22,
				24,
				49,
				35,
				37,
				29,
				40,
				58,
				51,
				61,
				60,
				57,
				51,
				56,
				55,
				64,
				72,
				92,
				78,
				64,
				68,
				87,
				69,
				55,
				56,
				80,
				109,
				81,
				87,
				95,
				98,
				103,
				104,
				103,
				62,
				77,
				113,
				121,
				112,
				100,
				120,
				92,
				101,
				103,
				99
			)
			
			std_chrominance_quant_tbl = intArrayOf(
				17,
				18,
				18,
				24,
				21,
				24,
				47,
				26,
				26,
				47,
				99,
				66,
				56,
				66,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99,
				99
			)
			
			deftabs = arrayOf(std_luminance_quant_tbl, std_chrominance_quant_tbl)
		}
		
		fun Get16m(data : ByteArray, position : Int) : Int {
			val b1 = data[position].toInt() and 0xFF shl 8
			val b2 = data[position + 1].toInt() and 0xFF
			return b1 or b2
		}
		
		/**
		 * Parses the the given InputStream with the given options
		 *
		 * @throws java.io.IOException
		 * @throws ExifInvalidFormatException
		 */
		@Throws(IOException::class, ExifInvalidFormatException::class)
		fun parse(inputStream : InputStream, options : Int, iRef : ExifInterface) : ExifParser =
			ExifParser(inputStream, options, iRef)
	}
}