/*
 * 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 java.nio.charset.Charset
import java.text.SimpleDateFormat
import java.util.*

/**
 * This class stores information of an EXIF tag. For more information about
 * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be
 * instantiated using [ExifInterface.buildTag].
 *
 * @see ExifInterface
 */
// Use builtTag in ExifInterface instead of constructor.
@Suppress("unused")
open class ExifTag internal constructor(
	
	// Exif TagId. the TID of this tag.
	val tagId : Short,
	
	// Exif Tag Type. the data type of this tag
	
	/*
	 * @see .TYPE_ASCII
	 * @see .TYPE_LONG
	 * @see .TYPE_RATIONAL
	 * @see .TYPE_UNDEFINED
	 * @see .TYPE_UNSIGNED_BYTE
	 * @see .TYPE_UNSIGNED_LONG
	 * @see .TYPE_UNSIGNED_RATIONAL
	 * @see .TYPE_UNSIGNED_SHORT
	 */
	val dataType : Short,
	
	componentCount : Int,
	
	// The ifd that this tag should be put in. the ID of the IFD this tag belongs to.
	/*
	 * @see IfdData.TYPE_IFD_0
	 * @see IfdData.TYPE_IFD_1
	 * @see IfdData.TYPE_IFD_EXIF
	 * @see IfdData.TYPE_IFD_GPS
	 * @see IfdData.TYPE_IFD_INTEROPERABILITY
	 */
	var ifd : Int,
	
	// If tag has defined count
	private var mHasDefinedDefaultComponentCount : Boolean
) {
	// Actual data count in tag (should be number of elements in value array)
	/**
	 * Gets the component count of this tag.
	 */
	
	// TODO: fix integer overflows with this
	var componentCount : Int = componentCount
		private set
	
	// The value (array of elements of type Tag Type)
	private var mValue : Any? = null
	
	// Value offset in exif header.  the offset of this tag.
	// This is only valid if this data size > 4 and contains an offset to the location of the actual value.
	var offset : Int = 0
	
	// the total data size in bytes of the value of this tag.
	val dataSize : Int
		get() = componentCount * getElementSize(dataType)
	
	/**
	 * Returns true if this ExifTag contains value; otherwise, this tag will
	 * contain an offset value that is determined when the tag is written.
	 */
	val hasValue :Boolean
		get() = mValue != null
	
	/**
	 * Gets the value as a byte array. This method should be used for tags of
	 * type [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
	 *
	 * @return the value as a byte array, or null if the tag's value does not
	 * exist or cannot be converted to a byte array.
	 */
	val valueAsBytes : ByteArray?
		get() = mValue as? ByteArray
	
	/**
	 * Gets the value as an array of longs. This method should be used for tags
	 * of type [.TYPE_UNSIGNED_LONG].
	 *
	 * @return the value as as an array of longs, or null if the tag's value
	 * does not exist or cannot be converted to an array of longs.
	 */
	val valueAsLongs : LongArray?
		get() = mValue as? LongArray
	
	/**
	 * Gets the value as an array of Rationals. This method should be used for
	 * tags of type [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
	 *
	 * @return the value as as an array of Rationals, or null if the tag's value
	 * does not exist or cannot be converted to an array of Rationals.
	 */
	@Suppress("UNCHECKED_CAST")
	val valueAsRationals : Array<Rational>?
		get() = mValue as? Array<Rational>
	
	/**
	 * Gets the value as an array of ints. This method should be used for tags
	 * of type [.TYPE_UNSIGNED_SHORT], [.TYPE_UNSIGNED_LONG].
	 *
	 * @return the value as as an array of ints, or null if the tag's value does
	 * not exist or cannot be converted to an array of ints.
	 */
	// Truncates
	val valueAsInts : IntArray?
		get() = when(val v = mValue) {
			is LongArray -> IntArray(v.size) { v[it].toInt() }
			else -> null
		}
	
	/**
	 * Gets the value as a String. This method should be used for tags of type
	 * [.TYPE_ASCII].
	 *
	 * @return the value as a String, or null if the tag's value does not exist
	 * or cannot be converted to a String.
	 */
	val valueAsString : String?
		get() = when(val v = mValue) {
			is String -> v
			is ByteArray -> String(v, US_ASCII)
			else -> null
		}
	
	/**
	 * Gets the [.TYPE_ASCII] data.
	 *
	 * @throws IllegalArgumentException If the type is NOT
	 * [.TYPE_ASCII].
	 */
	protected val string : String
		get() = valueAsString !!
	
	/*
	 * Get the converted ascii byte. Used by ExifOutputStream.
	 */
	val stringByte : ByteArray?
		get() = mValue as? ByteArray
	
	/**
	 * Sets the component count of this tag. Call this function before
	 * setValue() if the length of value does not match the component count.
	 */
	fun forceSetComponentCount(count : Int) {
		componentCount = count
	}
	
	/**
	 * Sets integer values into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_SHORT]. This method will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
	 * [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
	 *  * The value overflows.
	 *  * The value.length does NOT match the component count in the definition
	 * for this tag.
	 *
	 */
	fun setValue(value : IntArray) : Boolean {
		if(checkBadComponentCount(value.size)) {
			return false
		}
		if(dataType != TYPE_UNSIGNED_SHORT && dataType != TYPE_LONG &&
			dataType != TYPE_UNSIGNED_LONG) {
			return false
		}
		if(dataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) {
			return false
		} else if(dataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) {
			return false
		}
		
		val data = LongArray(value.size)
		for(i in value.indices) {
			data[i] = value[i].toLong()
		}
		mValue = data
		componentCount = value.size
		return true
	}
	
	/**
	 * Sets integer value into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_SHORT], or [.TYPE_LONG]. This method
	 * will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_SHORT],
	 * [.TYPE_UNSIGNED_LONG], or [.TYPE_LONG].
	 *  * The value overflows.
	 *  * The component count in the definition of this tag is not 1.
	 *
	 */
	fun setValue(value : Int) = setValue(intArrayOf(value))
	
	/**
	 * Sets long values into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_LONG]. This method will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
	 *  * The value overflows.
	 *  * The value.length does NOT match the component count in the definition
	 * for this tag.
	 *
	 */
	fun setValue(value : LongArray) : Boolean {
		if(checkBadComponentCount(value.size) || dataType != TYPE_UNSIGNED_LONG) {
			return false
		}
		if(checkOverflowForUnsignedLong(value)) {
			return false
		}
		mValue = value
		componentCount = value.size
		return true
	}
	
	/**
	 * Sets long values into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_LONG]. This method will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_LONG].
	 *  * The value overflows.
	 *  * The component count in the definition for this tag is not 1.
	 *
	 */
	fun setValue(value : Long) = setValue(longArrayOf(value))
	
	/**
	 * Sets Rational values into this tag. This method should be used for tags
	 * of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
	 * method will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
	 * or [.TYPE_RATIONAL].
	 *  * The value overflows.
	 *  * The value.length does NOT match the component count in the definition
	 * for this tag.
	 *
	 *
	 * @see Rational
	 */
	fun setValue(value : Array<Rational>) : Boolean {
		if(checkBadComponentCount(value.size)) {
			return false
		}
		if(dataType != TYPE_UNSIGNED_RATIONAL && dataType != TYPE_RATIONAL) {
			return false
		}
		if(dataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) {
			return false
		} else if(dataType == TYPE_RATIONAL && checkOverflowForRational(value)) {
			return false
		}
		
		mValue = value
		componentCount = value.size
		return true
	}
	
	/**
	 * Sets a Rational value into this tag. This method should be used for tags
	 * of type [.TYPE_UNSIGNED_RATIONAL], or [.TYPE_RATIONAL]. This
	 * method will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_RATIONAL]
	 * or [.TYPE_RATIONAL].
	 *  * The value overflows.
	 *  * The component count in the definition for this tag is not 1.
	 *
	 *
	 * @see Rational
	 */
	fun setValue(value : Rational) =setValue(arrayOf(value))
	
	/**
	 * Sets byte values into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
	 * will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
	 * [.TYPE_UNDEFINED] .
	 *  * The length does NOT match the component count in the definition for
	 * this tag.
	 *
	 */
	@JvmOverloads
	fun setValue(value : ByteArray, offset : Int = 0, length : Int = value.size) : Boolean {
		if(checkBadComponentCount(length)) {
			return false
		}
		if(dataType != TYPE_UNSIGNED_BYTE && dataType != TYPE_UNDEFINED) {
			return false
		}
		componentCount = length
		mValue = ByteArray(length).also {
			System.arraycopy(value, offset, it, 0, length)
		}
		return true
	}
	
	/**
	 * Sets byte value into this tag. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_BYTE] or [.TYPE_UNDEFINED]. This method
	 * will fail if:
	 *
	 *  * The component type of this tag is not [.TYPE_UNSIGNED_BYTE] or
	 * [.TYPE_UNDEFINED] .
	 *  * The component count in the definition for this tag is not 1.
	 *
	 */
	fun setValue(value : Byte) = setValue(byteArrayOf(value))
	
	/**
	 * Sets the value for this tag using an appropriate setValue method for the
	 * given object. This method will fail if:
	 *
	 *  * The corresponding setValue method for the class of the object passed
	 * in would fail.
	 *  * There is no obvious way to cast the object passed in into an EXIF tag
	 * type.
	 *
	 */
	inline fun <reified T : Any> setValueAny(obj : T) : Boolean {
		when(obj) {
			// null -> return false
			
			is String -> return setValue(obj)
			is ByteArray -> return setValue(obj)
			is IntArray -> return setValue(obj)
			is LongArray -> return setValue(obj)
			is Rational -> return setValue(obj)
			is Byte -> return setValue(obj.toByte())
			is Short -> return setValue(obj.toInt() and 0x0ffff)
			is Int -> return setValue(obj.toInt())
			is Long -> return setValue(obj.toLong())
			
			else -> {
				
				@Suppress("UNCHECKED_CAST")
				val ra = obj as? Array<Rational>
				if(ra != null) return setValue(ra)
				
				// Nulls in this array are treated as zeroes.
				@Suppress("UNCHECKED_CAST")
				val sa = obj as? Array<Short?>
				if(sa != null) return setValue(IntArray(sa.size) {
					(sa[it]?.toInt() ?: 0) and 0xffff
				})
				
				// Nulls in this array are treated as zeroes.
				@Suppress("UNCHECKED_CAST")
				val ia = obj as? Array<Int?>
				if(ia != null) return setValue(IntArray(ia.size) { ia[it] ?: 0 })
				
				// Nulls in this array are treated as zeroes.
				@Suppress("UNCHECKED_CAST")
				val la = obj as? Array<Long?>
				if(la != null) return setValue(LongArray(la.size) { la[it] ?: 0L })
				
				// Nulls in this array are treated as zeroes.
				@Suppress("UNCHECKED_CAST")
				val ba = obj as? Array<Byte?>
				if(ba != null) return setValue(ByteArray(ba.size) { ba[it] ?: 0 })
				
				return false
			}
		}
	}
	
	/**
	 * Sets a timestamp to this tag. The method converts the timestamp with the
	 * format of "yyyy:MM:dd kk:mm:ss" and calls [.setValue]. This
	 * method will fail if the data type is not [.TYPE_ASCII] or the
	 * component count of this tag is not 20 or undefined.
	 *
	 * @param time the number of milliseconds since Jan. 1, 1970 GMT
	 * @return true on success
	 */
	fun setValueTime(time : Long) : Boolean {
		// synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe
		synchronized(TIME_FORMAT) {
			return setValue(TIME_FORMAT.format(Date(time)))
		}
	}
	
	/**
	 * Sets a string value into this tag. This method should be used for tags of
	 * type [.TYPE_ASCII]. The string is converted to an ASCII string.
	 * Characters that cannot be converted are replaced with '?'. The length of
	 * the string must be equal to either (component count -1) or (component
	 * count). The final byte will be set to the string null terminator '\0',
	 * overwriting the last character in the string if the value.length is equal
	 * to the component count. This method will fail if:
	 *
	 *  * The data type is not [.TYPE_ASCII] or [.TYPE_UNDEFINED].
	 *  * The length of the string is not equal to (component count -1) or
	 * (component count) in the definition for this tag.
	 *
	 */
	fun setValue(value : String) : Boolean {
		if(dataType != TYPE_ASCII && dataType != TYPE_UNDEFINED) {
			return false
		}
		
		val buf = value.toByteArray(US_ASCII)
		
		val finalBuf = when {
			buf.isNotEmpty() -> when {
				buf[buf.size - 1].toInt() == 0 || dataType == TYPE_UNDEFINED -> buf
				else -> buf.copyOf(buf.size + 1)
			}
			dataType == TYPE_ASCII && componentCount == 1 -> byteArrayOf(0)
			else -> buf
		}
		val count = finalBuf.size
		if(checkBadComponentCount(count)) {
			return false
		}
		componentCount = count
		mValue = finalBuf
		return true
	}
	
	private fun checkBadComponentCount(count : Int) : Boolean {
		return mHasDefinedDefaultComponentCount && componentCount != count
	}
	
	/**
	 * Gets the value as a String. This method should be used for tags of type
	 * [.TYPE_ASCII].
	 *
	 * @param defaultValue the String to return if the tag's value does not
	 * exist or cannot be converted to a String.
	 * @return the tag's value as a String, or the defaultValue.
	 */
	fun getValueAsString(defaultValue : String) : String {
		return valueAsString ?: defaultValue
	}
	
	/**
	 * Gets the value as a byte. If there are more than 1 bytes in this value,
	 * gets the first byte. This method should be used for tags of type
	 * [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
	 *
	 * @param defaultValue the byte to return if tag's value does not exist or
	 * cannot be converted to a byte.
	 * @return the tag's value as a byte, or the defaultValue.
	 */
	fun getValueAsByte(defaultValue : Byte) : Byte {
		val array = valueAsBytes
		return if(array?.isNotEmpty() == true) array[0] else defaultValue
	}
	
	/**
	 * Gets the value as a Rational. If there are more than 1 Rationals in this
	 * value, gets the first one. This method should be used for tags of type
	 * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
	 *
	 * @param defaultValue the numerator of the Rational to return if tag's
	 * value does not exist or cannot be converted to a Rational (the
	 * denominator will be 1).
	 * @return the tag's value as a Rational, or the defaultValue.
	 */
	fun getValueAsRational(defaultValue : Long) : Rational =
		getValueAsRational(Rational(defaultValue, 1))
	
	/**
	 * Gets the value as a Rational. If there are more than 1 Rationals in this
	 * value, gets the first one. This method should be used for tags of type
	 * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
	 *
	 * @param defaultValue the Rational to return if tag's value does not exist
	 * or cannot be converted to a Rational.
	 * @return the tag's value as a Rational, or the defaultValue.
	 */
	private fun getValueAsRational(defaultValue : Rational) : Rational {
		val array = valueAsRationals
		return if(array?.isNotEmpty() == true) array[0] else defaultValue
	}
	
	/**
	 * Gets the value as an int. If there are more than 1 ints in this value,
	 * gets the first one. This method should be used for tags of type
	 * [.TYPE_UNSIGNED_SHORT], [.TYPE_UNSIGNED_LONG].
	 *
	 * @param defaultValue the int to return if tag's value does not exist or
	 * cannot be converted to an int.
	 * @return the tag's value as a int, or the defaultValue.
	 */
	fun getValueAsInt(defaultValue : Int) : Int {
		val array = valueAsInts
		return if(array?.isNotEmpty() == true) array[0] else defaultValue
	}
	
	/**
	 * Gets the value or null if none exists. If there are more than 1 longs in
	 * this value, gets the first one. This method should be used for tags of
	 * type [.TYPE_UNSIGNED_LONG].
	 *
	 * @param defaultValue the long to return if tag's value does not exist or
	 * cannot be converted to a long.
	 * @return the tag's value as a long, or the defaultValue.
	 */
	fun getValueAsLong(defaultValue : Long) : Long {
		val array = valueAsLongs
		return if(array?.isNotEmpty() == true) array[0] else defaultValue
	}
	
	/**
	 * Gets the tag's value or null if none exists.
	 */
	fun getValue() : Any? {
		return mValue
	}
	
	/**
	 * Gets a long representation of the value.
	 *
	 * @param defaultValue value to return if there is no value or value is a
	 * rational with a denominator of 0.
	 * @return the tag's value as a long, or defaultValue if no representation
	 * exists.
	 */
	fun forceGetValueAsLong(defaultValue : Long) : Long {
		when(val v = mValue) {
			is LongArray -> if(v.isNotEmpty()) return v[0]
			is ByteArray -> if(v.isNotEmpty()) return v[0].toLong()
			
			else -> {
				val r = valueAsRationals
				if(r?.isNotEmpty() == true && r[0].denominator != 0L) {
					return r[0].toDouble().toLong()
				}
			}
		}
		return defaultValue
	}
	
	/**
	 * Gets the value for type [.TYPE_ASCII], [.TYPE_LONG],
	 * [.TYPE_UNDEFINED], [.TYPE_UNSIGNED_BYTE],
	 * [.TYPE_UNSIGNED_LONG], or [.TYPE_UNSIGNED_SHORT]. For
	 * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL], call
	 * [.getRational] instead.
	 *
	 * @throws IllegalArgumentException if the data type is
	 * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
	 */
	fun getValueAt(index : Int) : Long {
		return when(val v = mValue) {
			is LongArray -> v[index]
			is ByteArray -> v[index].toLong()
			else -> error(
				"Cannot get integer value from ${convertTypeToString(dataType)}"
			)
		}
	}
	
	/**
	 * Gets the [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL] data.
	 *
	 * @throws IllegalArgumentException If the type is NOT
	 * [.TYPE_RATIONAL] or [.TYPE_UNSIGNED_RATIONAL].
	 */
	fun getRational(index : Int) : Rational? {
		require(! (dataType != TYPE_RATIONAL && dataType != TYPE_UNSIGNED_RATIONAL)) {
			"Cannot get RATIONAL value from " + convertTypeToString(dataType)
		}
		return valueAsRationals?.get(index)
	}
	
	/**
	 * Gets the [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE] data.
	 *
	 * @param buf    the byte array in which to store the bytes read.
	 * @param offset the initial position in buffer to store the bytes.
	 * @param length the maximum number of bytes to store in buffer. If length >
	 * component count, only the valid bytes will be stored.
	 * @throws IllegalArgumentException If the type is NOT
	 * [.TYPE_UNDEFINED] or [.TYPE_UNSIGNED_BYTE].
	 */
	@JvmOverloads
	fun getBytes(buf : ByteArray, offset : Int = 0, length : Int = buf.size) {
		require(! (dataType != TYPE_UNDEFINED && dataType != TYPE_UNSIGNED_BYTE)) {
			"Cannot get BYTE value from " + convertTypeToString(
				dataType
			)
		}
		System.arraycopy(
			mValue !!,
			0,
			buf,
			offset,
			if(length > componentCount) componentCount else length
		)
	}
	
	var hasDefinedCount : Boolean
		get() = mHasDefinedDefaultComponentCount
		set(value) {
			mHasDefinedDefaultComponentCount = value
		}
	
	private fun checkOverflowForUnsignedShort(value : IntArray) : Boolean =
		null != value.find { it !in 0 .. UNSIGNED_SHORT_MAX }
	
	private fun checkOverflowForUnsignedLong(value : LongArray) : Boolean =
		null != value.find { it !in 0 .. UNSIGNED_LONG_MAX }
	
	private fun checkOverflowForUnsignedLong(value : IntArray) : Boolean =
		null != value.find { it < 0 }
	
	private fun checkOverflowForUnsignedRational(value : Array<Rational>) : Boolean =
		null != value.find { it.numerator !in 0 .. UNSIGNED_LONG_MAX || it.denominator !in 0 .. UNSIGNED_LONG_MAX }
	
	private fun checkOverflowForRational(value : Array<Rational>) : Boolean =
		null != value.find { it.numerator !in LONG_MIN .. LONG_MAX || it.denominator !in LONG_MIN .. LONG_MAX }
	
	override fun hashCode() : Int {
		var result = tagId.toInt()
		result = 31 * result + dataType.toInt()
		result = 31 * result + ifd
		result = 31 * result + componentCount
		result = 31 * result + offset
		result = 31 * result + mHasDefinedDefaultComponentCount.hashCode()
		result = 31 * result + (mValue?.hashCode() ?: 0)
		return result
	}
	
	override fun equals(other : Any?) : Boolean {
		if(other !is ExifTag) return false
		
		if(other.tagId != this.tagId
			|| other.componentCount != this.componentCount
			|| other.dataType != this.dataType
		) {
			return false
		}
		
		val va = this.mValue
		val vb = other.mValue
		
		return when {
			
			va == null -> vb == null
			
			vb == null -> false
			
			va is LongArray -> when(vb) {
				is LongArray -> Arrays.equals(va, vb)
				else -> false
			}
			
			va is ByteArray -> when(vb) {
				is ByteArray -> Arrays.equals(va, vb)
				else -> false
			}
			
			va is Array<*> && va.isArrayOf<Rational>() -> when {
				vb is Array<*> && vb.isArrayOf<Rational>() -> Arrays.equals(va, vb)
				else -> false
			}
			
			else -> va == vb
		}
	}
	
	override fun toString() : String {
		val strTagId = String.format("%04X", tagId)
		return "tag id: $strTagId\nifd id: $ifd\ntype: ${convertTypeToString(dataType)}\ncount: $componentCount\noffset: $offset\nvalue: ${forceGetValueAsString()}\n"
	}
	
	/**
	 * Gets a string representation of the value.
	 */
	private fun forceGetValueAsString() : String {
		when(val v = mValue) {
			
			null -> return ""
			
			is ByteArray -> return when(dataType) {
				TYPE_ASCII -> String(v, US_ASCII)
				else -> Arrays.toString(v)
			}
			
			is LongArray -> return when {
				v.size == 1 -> v[0].toString()
				else -> Arrays.toString(v)
			}
			
			is Array<*> -> return when {
				v.size == 1 -> v[0]?.toString() ?: ""
				else -> Arrays.toString(v)
			}
			
			else -> return v.toString()
		}
	}
	
	companion object {
		/**
		 * The BYTE type in the EXIF standard. An 8-bit unsigned integer.
		 */
		const val TYPE_UNSIGNED_BYTE : Short = 1
		/**
		 * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit
		 * ASCII code. The final byte is terminated with NULL.
		 */
		const val TYPE_ASCII : Short = 2
		/**
		 * The SHORT type in the EXIF standard. A 16-bit (2-byte) unsigned integer
		 */
		const val TYPE_UNSIGNED_SHORT : Short = 3
		/**
		 * The LONG type in the EXIF standard. A 32-bit (4-byte) unsigned integer
		 */
		const val TYPE_UNSIGNED_LONG : Short = 4
		/**
		 * The RATIONAL type of EXIF standard. It consists of two LONGs. The first
		 * one is the numerator and the second one expresses the denominator.
		 */
		const val TYPE_UNSIGNED_RATIONAL : Short = 5
		/**
		 * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any
		 * value depending on the field definition.
		 */
		const val TYPE_UNDEFINED : Short = 7
		/**
		 * The SLONG type in the EXIF standard. A 32-bit (4-byte) signed integer
		 * (2's complement notation).
		 */
		const val TYPE_LONG : Short = 9
		/**
		 * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first
		 * one is the numerator and the second one is the denominator.
		 */
		const val TYPE_RATIONAL : Short = 10
		internal const val SIZE_UNDEFINED = 0
		private val TYPE_TO_SIZE_MAP = IntArray(11)
		private const val UNSIGNED_SHORT_MAX = 65535
		private const val UNSIGNED_LONG_MAX = 4294967295L
		private const val LONG_MAX = Integer.MAX_VALUE.toLong()
		private const val LONG_MIN = Integer.MIN_VALUE.toLong()
		
		init {
			TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE.toInt()] = 1
			TYPE_TO_SIZE_MAP[TYPE_ASCII.toInt()] = 1
			TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_SHORT.toInt()] = 2
			TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_LONG.toInt()] = 4
			TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_RATIONAL.toInt()] = 8
			TYPE_TO_SIZE_MAP[TYPE_UNDEFINED.toInt()] = 1
			TYPE_TO_SIZE_MAP[TYPE_LONG.toInt()] = 4
			TYPE_TO_SIZE_MAP[TYPE_RATIONAL.toInt()] = 8
		}
		
		private val TIME_FORMAT = SimpleDateFormat("yyyy:MM:dd kk:mm:ss", Locale.ENGLISH)
		private val US_ASCII = Charset.forName("US-ASCII")
		
		/**
		 * Returns true if the given IFD is a valid IFD.
		 */
		fun isValidIfd(ifdId : Int) : Boolean =
			when(ifdId) {
				IfdData.TYPE_IFD_0,
				IfdData.TYPE_IFD_1,
				IfdData.TYPE_IFD_EXIF,
				IfdData.TYPE_IFD_INTEROPERABILITY,
				IfdData.TYPE_IFD_GPS -> true
				else -> false
			}
		
		/**
		 * Returns true if a given type is a valid tag type.
		 */
		fun isValidType(type : Short) : Boolean =
			when(type) {
				TYPE_UNSIGNED_BYTE,
				TYPE_ASCII,
				TYPE_UNSIGNED_SHORT,
				TYPE_UNSIGNED_LONG,
				TYPE_UNSIGNED_RATIONAL,
				TYPE_UNDEFINED,
				TYPE_LONG,
				TYPE_RATIONAL -> true
				else -> false
			}
		
		/**
		 * Gets the element size of the given data type in bytes.
		 *
		 * @see .TYPE_ASCII
		 * @see .TYPE_LONG
		 * @see .TYPE_RATIONAL
		 * @see .TYPE_UNDEFINED
		 * @see .TYPE_UNSIGNED_BYTE
		 * @see .TYPE_UNSIGNED_LONG
		 * @see .TYPE_UNSIGNED_RATIONAL
		 * @see .TYPE_UNSIGNED_SHORT
		 */
		fun getElementSize(type : Short) : Int = TYPE_TO_SIZE_MAP[type.toInt()]
		
		private fun convertTypeToString(type : Short) : String =
			when(type) {
				TYPE_UNSIGNED_BYTE -> "UNSIGNED_BYTE"
				TYPE_ASCII -> "ASCII"
				TYPE_UNSIGNED_SHORT -> "UNSIGNED_SHORT"
				TYPE_UNSIGNED_LONG -> "UNSIGNED_LONG"
				TYPE_UNSIGNED_RATIONAL -> "UNSIGNED_RATIONAL"
				TYPE_UNDEFINED -> "UNDEFINED"
				TYPE_LONG -> "LONG"
				TYPE_RATIONAL -> "RATIONAL"
				else -> ""
			}
	}
}
