import util from './util'
import TiffTags from './tiff_tags'
import ExifTags from './tags'
import StringValues from './string_values'
import GPSTags from './gps_tags'
import { DOMParser } from '@xmldom/xmldom'

export const getData = function (img, callback) {
  if (!util.imageHasData(img)) {
    getImageData(img, callback)
  } else {
    if (callback) {
      callback(img)
    }
  }
  return true
}

function getImageData (img, callback) {
  try {
    var data = findEXIFinJPEG(img)
    img.exifdata = data.exif
    img.xmpdata = data.xmp
    if (callback) {
      callback(img)
    }
  } catch (err) {
    img.exifdata = {}
    img.xmpdata = {}
    callback(img, err)
  }
}

function findEXIFinJPEG (file) {
  var dataView = new DataView(file)
  let exif, xmp

  var offset = 2,
    length = file.byteLength,
    dom = new DOMParser(),
    marker

  while (offset < length) {
    if (dataView.getUint8(offset) != 0xFF) {
      break
    }

    marker = dataView.getUint8(offset + 1)

    // we could implement handling for other markers here,
    // but we're only looking for 0xFFE1 for EXIF data

    if (marker == 225) {
      if (util.getStringFromDB(dataView, offset + 4, 4) == 'http') { // 这个是‘xmp’
        var startOffset = offset + 3
        var sectionLength = dataView.getUint16(offset + 2) - 1
        var xmpString = util.getStringFromDB(dataView, startOffset, sectionLength)
        var xmpEndIndex = xmpString.indexOf('xmpmeta>') + 8
        xmpString = xmpString.substring(xmpString.indexOf('<x:xmpmeta'), xmpEndIndex)
  
        var indexOfXmp = xmpString.indexOf('x:xmpmeta') + 10
        //Many custom written programs embed xmp/xml without any namespace. Following are some of them.
        //Without these namespaces, XML is thought to be invalid by parsers
        xmpString = xmpString.slice(0, indexOfXmp)
          + 'xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" '
          + 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
          + 'xmlns:tiff="http://ns.adobe.com/tiff/1.0/" '
          + 'xmlns:plus="http://schemas.android.com/apk/lib/com.google.android.gms.plus" '
          + 'xmlns:ext="http://www.gettyimages.com/xsltExtension/1.0" '
          + 'xmlns:exif="http://ns.adobe.com/exif/1.0/" '
          + 'xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#" '
          + 'xmlns:stRef="http://ns.adobe.com/xap/1.0/sType/ResourceRef#" '
          + 'xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" '
          + 'xmlns:xapGImg="http://ns.adobe.com/xap/1.0/g/img/" '
          + 'xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/" '
          + xmpString.slice(indexOfXmp)
        
        var domDocument = dom.parseFromString(xmpString, 'text/xml')
        xmp = util.xml2Object(domDocument)
      } else { // 这个是exif
        exif = readEXIFData(dataView, offset + 4, dataView.getUint16(offset + 2) - 2)
      }
    }
    if (xmp && exif) {
      break
    }
    offset += 2 + dataView.getUint16(offset + 2)
  }
  return {exif: exif || {}, xmp: xmp || {}}

}


function readEXIFData (file, start) {
  if (util.getStringFromDB(file, start, 4) != 'Exif') {
    return false
  }

  var bigEnd,
    tags, tag,
    exifData, gpsData,
    tiffOffset = start + 6

  // test for TIFF validity and endianness
  if (file.getUint16(tiffOffset) == 0x4949) {
    bigEnd = false
  } else if (file.getUint16(tiffOffset) == 0x4D4D) {
    bigEnd = true
  } else {
    return false
  }

  if (file.getUint16(tiffOffset + 2, !bigEnd) != 0x002A) {
    return false
  }

  var firstIFDOffset = file.getUint32(tiffOffset + 4, !bigEnd)

  if (firstIFDOffset < 0x00000008) {
    return false
  }

  tags = readTags(file, tiffOffset, tiffOffset + firstIFDOffset, TiffTags, bigEnd)

  if (tags.ExifIFDPointer) {
    exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd)
    for (tag in exifData) {
      switch (tag) {
        case 'LightSource':
        case 'Flash':
        case 'MeteringMode':
        case 'ExposureProgram':
        case 'SensingMethod':
        case 'SceneCaptureType':
        case 'SceneType':
        case 'CustomRendered':
        case 'WhiteBalance':
        case 'GainControl':
        case 'Contrast':
        case 'Saturation':
        case 'Sharpness':
        case 'SubjectDistanceRange':
        case 'FileSource':
          exifData[tag] = StringValues[tag][exifData[tag]]
          break

        case 'ExifVersion':
        case 'FlashpixVersion':
          exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3])
          break

        case 'ComponentsConfiguration':
          exifData[tag] =
            StringValues.Components[exifData[tag][0]] +
            StringValues.Components[exifData[tag][1]] +
            StringValues.Components[exifData[tag][2]] +
            StringValues.Components[exifData[tag][3]]
          break
      }
      tags[tag] = exifData[tag]
    }
  }

  if (tags.GPSInfoIFDPointer) {
    gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd)
    for (tag in gpsData) {
      switch (tag) {
        case 'GPSVersionID':
          gpsData[tag] = gpsData[tag][0] +
            '.' + gpsData[tag][1] +
            '.' + gpsData[tag][2] +
            '.' + gpsData[tag][3]
          break
      }
      tags[tag] = gpsData[tag]
    }
  }

  // extract thumbnail
  //tags['thumbnail'] = readThumbnailImage(file, tiffOffset, firstIFDOffset, bigEnd);

  return tags
}

function readTags (file, tiffStart, dirStart, strings, bigEnd) {
  var entries = file.getUint16(dirStart, !bigEnd),
    tags = {},
    entryOffset, tag,
    i

  for (i = 0; i < entries; i++) {
    entryOffset = dirStart + i * 12 + 2
    tag = strings[file.getUint16(entryOffset, !bigEnd)]
    tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd)
  }
  return tags
}

function readTagValue (file, entryOffset, tiffStart, dirStart, bigEnd) {
  var type = file.getUint16(entryOffset + 2, !bigEnd),
    numValues = file.getUint32(entryOffset + 4, !bigEnd),
    valueOffset = file.getUint32(entryOffset + 8, !bigEnd) + tiffStart,
    offset,
    vals, val, n,
    numerator, denominator

  switch (type) {
    case 1: // byte, 8-bit unsigned int
    case 7: // undefined, 8-bit byte, value depending on field
      if (numValues == 1) {
        return file.getUint8(entryOffset + 8, !bigEnd)
      } else {
        offset = numValues > 4 ? valueOffset : (entryOffset + 8)
        vals = []
        for (n = 0; n < numValues; n++) {
          vals[n] = file.getUint8(offset + n)
        }
        return vals
      }

    case 2: // ascii, 8-bit byte
      offset = numValues > 4 ? valueOffset : (entryOffset + 8)
      return util.getStringFromDB(file, offset, numValues - 1)

    case 3: // short, 16 bit int
      if (numValues == 1) {
        return file.getUint16(entryOffset + 8, !bigEnd)
      } else {
        offset = numValues > 2 ? valueOffset : (entryOffset + 8)
        vals = []
        for (n = 0; n < numValues; n++) {
          vals[n] = file.getUint16(offset + 2 * n, !bigEnd)
        }
        return vals
      }

    case 4: // long, 32 bit int
      if (numValues == 1) {
        return file.getUint32(entryOffset + 8, !bigEnd)
      } else {
        vals = []
        for (n = 0; n < numValues; n++) {
          vals[n] = file.getUint32(valueOffset + 4 * n, !bigEnd)
        }
        return vals
      }

    case 5:    // rational = two long values, first is numerator, second is denominator
      if (numValues == 1) {
        numerator = file.getUint32(valueOffset, !bigEnd)
        denominator = file.getUint32(valueOffset + 4, !bigEnd)
        val = new Number(numerator / denominator)
        val.numerator = numerator
        val.denominator = denominator
        return val
      } else {
        vals = []
        for (n = 0; n < numValues; n++) {
          numerator = file.getUint32(valueOffset + 8 * n, !bigEnd)
          denominator = file.getUint32(valueOffset + 4 + 8 * n, !bigEnd)
          vals[n] = new Number(numerator / denominator)
          vals[n].numerator = numerator
          vals[n].denominator = denominator
        }
        return vals
      }

    case 9: // slong, 32 bit signed int
      if (numValues == 1) {
        return file.getInt32(entryOffset + 8, !bigEnd)
      } else {
        vals = []
        for (n = 0; n < numValues; n++) {
          vals[n] = file.getInt32(valueOffset + 4 * n, !bigEnd)
        }
        return vals
      }

    case 10: // signed rational, two slongs, first is numerator, second is denominator
      if (numValues == 1) {
        return file.getInt32(valueOffset, !bigEnd) / file.getInt32(valueOffset + 4, !bigEnd)
      } else {
        vals = []
        for (n = 0; n < numValues; n++) {
          vals[n] = file.getInt32(valueOffset + 8 * n, !bigEnd) / file.getInt32(valueOffset + 4 + 8 * n, !bigEnd)
        }
        return vals
      }
  }
}
