/*global module*/

//ftp://medical.nema.org/MEDICAL/Dicom/2014c/output/chtml/part05/sect_6.2.html/

'use strict';

// imports
var dicomParser = require('dicom-parser');
var jpx = require('./jpx.js');

var VJS = VJS || {};
VJS.parsers = VJS.parsers || {};

/**
 * Dicom parser is a combination of utilities to get a VJS image from dicom files.
 *
 * Relies on dcmjs, jquery, HTML5 fetch API, HTML5 promise API.
 *
 * @constructor
 * @class
 * @memberOf VJS.parsers
 * @public
 *
 * @param arrayBuffer {arraybuffer} - List of files to be parsed. It is urls from which
 * VJS.parsers.dicom can pull the data from.
 */
VJS.parsers.dicom = function(arrayBuffer, id) {
  /**
   * @member
   * @type {arraybuffer}
   */
  this._id = id;
  this._arrayBuffer = arrayBuffer;

  var byteArray = new Uint8Array(arrayBuffer);
  // window.console.log(byteArray.length);

  // catch error
  // throw error if any!
  this._dataSet = null;
  try {
    this._dataSet = dicomParser.parseDicom(byteArray);
  }
  catch (e) {
    window.console.log(e);
    throw 'VJS.parsers.dicom could not parse the dicom';
  }

  //window.console.log(dicomParser);
  // window.console.log(this._dataSet);
  // this.rescaleIntercept(0);
};

VJS.parsers.dicom.prototype.seriesInstanceUID =  function() {
  return this._dataSet.string('x0020000e');
};

VJS.parsers.dicom.prototype.modality =  function() {
  return this._dataSet.string('x00080060');
};

// image/frame specific
VJS.parsers.dicom.prototype.sopInstanceUID =  function() {
  return this._dataSet.string('x00200018');
};

VJS.parsers.dicom.prototype.transferSyntaxUID =  function() {
  return this._dataSet.string('x00020010');
};

VJS.parsers.dicom.prototype.photometricInterpretation =  function() {
  return this._dataSet.string('x00280004');
};

VJS.parsers.dicom.prototype.planarConfiguration =  function() {

  var planarConfiguration = this._dataSet.uint16('x00280006');

  if (typeof planarConfiguration === 'undefined') {
    planarConfiguration = null;
  }

  return planarConfiguration;
};

VJS.parsers.dicom.prototype.samplesPerPixel =  function() {
  return this._dataSet.uint16('x00280002');
};

VJS.parsers.dicom.prototype.numberOfFrames =  function() {
  var numberOfFrames = this._dataSet.intString('x00280008');

  // need something smarter!
  if (typeof numberOfFrames === 'undefined') {
    numberOfFrames = null;
  }

  // make sure we return a number! (not a string!)
  return numberOfFrames;
};

VJS.parsers.dicom.prototype.numberOfChannels =  function() {
  var numberOfChannels = 1;
  var photometricInterpretation = this.photometricInterpretation();

  if (photometricInterpretation === 'RGB' ||
            photometricInterpretation === 'PALETTE COLOR' ||
            photometricInterpretation === 'YBR_FULL' ||
            photometricInterpretation === 'YBR_FULL_422' ||
            photometricInterpretation === 'YBR_PARTIAL_422' ||
            photometricInterpretation === 'YBR_PARTIAL_420' ||
            photometricInterpretation === 'YBR_RCT') {
    numberOfChannels = 3;
  }

  // make sure we return a number! (not a string!)
  return numberOfChannels;
};

VJS.parsers.dicom.prototype.imageOrientation =  function(frameIndex) {
  // expect frame index to start at 0!
  var imageOrientation = this._dataSet.string('x00200037');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof imageOrientation === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // plane orientation sequence for Nth element in the sequence
      var planeOrientationSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00209116.items[0].dataSet;
      imageOrientation = planeOrientationSequence.string('x00200037');
    } else {
      // default orientation
      // should we default to undefined??
      imageOrientation = null;
    }
  }

  // format image orientation ('1\0\0\0\1\0') to array containing 6 numbers
  if (imageOrientation) {
    // make sure we return a number! (not a string!)
    // might not need to split (floatString + index)
    imageOrientation = imageOrientation.split('\\').map(Number);
  }

  return imageOrientation;
};

VJS.parsers.dicom.prototype.pixelAspectRatio =  function() {
  var pixelAspectRatio = [
    this._dataSet.intString('x00280034', 0),
    this._dataSet.intString('x00280034', 1)
    ];

  // need something smarter!
  if (typeof pixelAspectRatio[0] === 'undefined') {
    pixelAspectRatio = null;
  }

  // make sure we return a number! (not a string!)
  return pixelAspectRatio;
};

VJS.parsers.dicom.prototype.imagePosition =  function(frameIndex) {
  var imagePosition = null;
  // first look for frame!
  // per frame functionnal group sequence
  var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

  if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
    // plane orientation sequence for Nth element in the sequence
    var planeOrientationSequence = perFrameFunctionnalGroupSequence
      .items[frameIndex].dataSet.elements.x00209113.items[0].dataSet;
    imagePosition = planeOrientationSequence.string('x00200032');
  } else {
    // should we default to undefined??
    // default orientation
    imagePosition = this._dataSet.string('x00200032');

    if (typeof imagePosition === 'undefined') {
      imagePosition = null;
    }
  }

  // format image orientation ('1\0\0\0\1\0') to array containing 6 numbers
  if (imagePosition) {
    // make sure we return a number! (not a string!)
    imagePosition = imagePosition.split('\\').map(Number);
  }

  return imagePosition;
};

VJS.parsers.dicom.prototype.instanceNumber =  function(frameIndex) {
  var instanceNumber = null;
  // first look for frame!
  // per frame functionnal group sequence
  var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

  if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
    // plane orientation sequence for Nth element in the sequence
    // PHILIPS HACK...
    if (perFrameFunctionnalGroupSequence
      .items[frameIndex].dataSet.elements.x2005140f) {
      var planeOrientationSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x2005140f.items[0].dataSet;
      instanceNumber = planeOrientationSequence.intString('x00200013');
    } else {
      instanceNumber = this._dataSet.intString('x00200013');

      if (typeof instanceNumber === 'undefined') {
        instanceNumber = null;
      }
    }

  } else {
    // should we default to undefined??
    // default orientation
    instanceNumber = this._dataSet.intString('x00200013');

    if (typeof instanceNumber === 'undefined') {
      instanceNumber = null;
    }
  }

  return instanceNumber;
};

VJS.parsers.dicom.prototype.pixelSpacing =  function(frameIndex) {
  // expect frame index to start at 0!
  var pixelSpacing = this._dataSet.string('x00280030');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof pixelSpacing === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // plane orientation sequence for Nth element in the sequence
      var planeOrientationSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289110.items[0].dataSet;
      pixelSpacing = planeOrientationSequence.string('x00280030');
    } else {
      // default orientation
      pixelSpacing = null;
    }
  }

  // format image orientation ('1\0\0\0\1\0') to array containing 6 numbers
  // should we default to undefined??
  if (pixelSpacing) {

    // make sure we return array of numbers! (not strings!)
    pixelSpacing = pixelSpacing.split('\\').map(Number);
  }
  return pixelSpacing;
};

VJS.parsers.dicom.prototype.sopInstanceUID =  function(frameIndex) {
  // expect frame index to start at 0!

  // per frame
  // philips 2005,140f
  //
  var sopInstanceUID = this._dataSet.string('x00080018');
  return sopInstanceUID;
};

VJS.parsers.dicom.prototype.sliceThickness =  function(frameIndex) {
  // expect frame index to start at 0!
  var sliceThickness = this._dataSet.floatString('x00180050');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof sliceThickness === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // plane orientation sequence for Nth element in the sequence
      var planeOrientationSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289110.items[0].dataSet;
      sliceThickness = planeOrientationSequence.floatString('x00180050');
    } else {
      // default orientation
      // should we default to undefined??
      // print warning at least...
      sliceThickness = null;
    }
  }

  return sliceThickness;
};

VJS.parsers.dicom.prototype.rows =  function(frameIndex) {
  // expect frame index to start at 0!
  var rows = this._dataSet.uint16('x00280010');

  if (typeof rows === 'undefined') {
    rows = null;
    // print warning at least...
  }

  return rows;
};

VJS.parsers.dicom.prototype.columns =  function(frameIndex) {
  // expect frame index to start at 0!
  var columns = this._dataSet.uint16('x00280011');

  if (typeof columns === 'undefined') {
    columns = null;
    // print warning at least...
  }

  return columns;
};

VJS.parsers.dicom.prototype.pixelRepresentation =  function(frameIndex) {
  // expect frame index to start at 0!
  var pixelRepresentation = this._dataSet.uint16('x00280103');
  return pixelRepresentation;
};

VJS.parsers.dicom.prototype.bitsAllocated =  function(frameIndex) {
  // expect frame index to start at 0!
  var bitsAllocated = this._dataSet.uint16('x00280100');
  return bitsAllocated;
};

VJS.parsers.dicom.prototype.highBit =  function(frameIndex) {
  // expect frame index to start at 0!
  var highBit = this._dataSet.uint16('x00280102');
  return highBit;
};

VJS.parsers.dicom.prototype.rescaleIntercept =  function(frameIndex) {
  // expect frame index to start at 0!
  var rescaleIntercept = this._dataSet.floatString('x00281052');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof rescaleIntercept === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // NOT A PHILIPS TRICK!
      var philipsPrivateSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289145.items[0].dataSet;
      rescaleIntercept = philipsPrivateSequence.floatString('x00281052');
    } else {
      // default rescaleIntercept
      rescaleIntercept = null;
    }
  }

  return rescaleIntercept;
};

VJS.parsers.dicom.prototype.rescaleSlope =  function(frameIndex) {
  // expect frame index to start at 0!
  var rescaleSlope = this._dataSet.floatString('x00281053');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof rescaleSlope === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // NOT A PHILIPS TRICK!
      var philipsPrivateSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289145.items[0].dataSet;
      rescaleSlope = philipsPrivateSequence.floatString('x00281052');
    } else {
      // default rescaleSlope
      rescaleSlope = null;
    }
  }

  return rescaleSlope;
};

VJS.parsers.dicom.prototype.windowCenter =  function(frameIndex) {
  // expect frame index to start at 0!
  var windowCenter = this._dataSet.floatString('x00281050');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof windowCenter === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // NOT A PHILIPS TRICK!.
      var philipsPrivateSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289132.items[0].dataSet;
      windowCenter = philipsPrivateSequence.floatString('x00281050');
    } else {
      // default windowCenter
      // print warning at least...
      windowCenter = null;
    }
  }

  return windowCenter;
};

VJS.parsers.dicom.prototype.windowWidth =  function(frameIndex) {
  // expect frame index to start at 0!
  var windowWidth = this._dataSet.floatString('x00281051');

  // try to get it from enhanced MR images
  // per-frame functionnal group
  if (typeof windowWidth === 'undefined') {
    // per frame functionnal group sequence
    var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

    if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
      // NOT A PHILIPS TRICK!
      var philipsPrivateSequence = perFrameFunctionnalGroupSequence
        .items[frameIndex].dataSet.elements.x00289132.items[0].dataSet;
      windowWidth = philipsPrivateSequence.floatString('x00281051');
    } else {
      // default windowWidth
      // print warning at least...
      windowWidth = null;
    }
  }
  return windowWidth;
};

VJS.parsers.dicom.prototype.dimensionIndexValues =  function(frameIndex) {
  var dimensionIndexValues = [];

  // try to get it from enhanced MR images
  // per-frame functionnal group sequence
  var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

  if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
    // NOT A PHILIPS TRICK!
    var philipsPrivateSequence = perFrameFunctionnalGroupSequence
      .items[frameIndex].dataSet.elements.x00209111.items[0].dataSet;
    var element = philipsPrivateSequence.elements.x00209157;
    // /4 because UL
    var nbValues = element.length / 4;
    for (var i = 0; i < nbValues; i++) {
      dimensionIndexValues.push(philipsPrivateSequence.uint32('x00209157', i));
    }
  } else {
    dimensionIndexValues = null;
  }

  return dimensionIndexValues;
};

VJS.parsers.dicom.prototype.inStackPositionNumber =  function(frameIndex) {
  var inStackPositionNumber = null;

  // try to get it from enhanced MR images
  // per-frame functionnal group sequence
  var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

  if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
    // NOT A PHILIPS TRICK!
    var philipsPrivateSequence = perFrameFunctionnalGroupSequence
      .items[frameIndex].dataSet.elements.x00209111.items[0].dataSet;
    inStackPositionNumber = philipsPrivateSequence.uint32('x00209057');
  } else {
    inStackPositionNumber = null;
  }

  return inStackPositionNumber;
};

VJS.parsers.dicom.prototype.stackID =  function(frameIndex) {
  var stackID = null;

  // try to get it from enhanced MR images
  // per-frame functionnal group sequence
  var perFrameFunctionnalGroupSequence = this._dataSet.elements.x52009230;

  if (typeof perFrameFunctionnalGroupSequence !== 'undefined') {
    // NOT A PHILIPS TRICK!
    var philipsPrivateSequence = perFrameFunctionnalGroupSequence
      .items[frameIndex].dataSet.elements.x00209111.items[0].dataSet;
    stackID = philipsPrivateSequence.intString('x00209056');
  } else {
    stackID = null;
  }

  return stackID;
};

VJS.parsers.dicom.prototype.decompressPixelData =  function(frameIndex) {
  // expect frame index to start at 0!
  var dPixelData = [];
  // http://www.dicomlibrary.com/dicom/transfer-syntax/
  var transferSyntaxUID = this.transferSyntaxUID();

  // find compression scheme
  if (transferSyntaxUID === '1.2.840.10008.1.2.4.90' ||  // JPEG 2000 lossless
      transferSyntaxUID === '1.2.840.10008.1.2.4.91') {  // JPEG 2000 lossy
    //window.console.log('JPG2000 in action!');
    // window.console.log(this._dataSet);
    //window.console.log(dicomParser);
    //window.console.log(this._dataSet.elements);
    var compressedPixelData = dicomParser.readEncapsulatedPixelData(this._dataSet, this._dataSet.elements.x7fe00010, frameIndex);
    // var pixelDataElement = this._dataSet.elements.x7fe00010;
    // var pixelData = new Uint8Array(this._dataSet.byteArray.buffer, pixelDataElement.dataOffset, pixelDataElement.length);
    var jpxImage = new jpx();
    // https://github.com/OHIF/image-JPEG2000/issues/6
    // It currently returns either Int16 or Uint16 based on whether the codestream is signed or not.
    jpxImage.parse(compressedPixelData);

    // var j2kWidth = jpxImage.width;
    // var j2kHeight = jpxImage.height;

    var componentsCount = jpxImage.componentsCount;
    if (componentsCount !== 1) {
      throw 'JPEG2000 decoder returned a componentCount of ' + componentsCount + ', when 1 is expected';
    }
    var tileCount = jpxImage.tiles.length;
    if (tileCount !== 1) {
      throw 'JPEG2000 decoder returned a tileCount of ' + tileCount + ', when 1 is expected';
    }
    var tileComponents = jpxImage.tiles[0];
    var pixelData = tileComponents.items;

    // window.console.log(j2kWidth, j2kHeight);

    return pixelData;
  }

  return dPixelData;
};

VJS.parsers.dicom.prototype.extractPixelData =  function(frameIndex) {
  // expect frame index to start at 0!
  var ePixelData = null;

  // if compressed..?
  var transferSyntaxUID = this.transferSyntaxUID();

  // find compression scheme
  if (transferSyntaxUID === '1.2.840.10008.1.2.4.90' ||  // JPEG 2000 lossless
      transferSyntaxUID === '1.2.840.10008.1.2.4.91') {
    // which format
    return this.decompressPixelData(frameIndex);
  }

  // else
  // ned to guess pixel format to know if uint8, unit16 or int16
  // Note - we may want to sanity check the rows * columns * bitsAllocated * samplesPerPixel against the buffer size
  var pixelRepresentation = this.pixelRepresentation(frameIndex);
  var bitsAllocated = this.bitsAllocated(frameIndex);
  var pixelDataElement = this._dataSet.elements.x7fe00010;
  var pixelDataOffset = pixelDataElement.dataOffset;
  var numberOfChannels  = this.numberOfChannels();
  var numPixels = this.rows(frameIndex) * this.columns(frameIndex) * numberOfChannels;
  var frameOffset = 0;

  if (numberOfChannels === 1) {
    if (pixelRepresentation === 0 && bitsAllocated === 8) {

      // unsigned 8 bit
      frameOffset = pixelDataOffset + frameIndex * numPixels;
      ePixelData =  new Uint8Array(this._dataSet.byteArray.buffer, frameOffset, numPixels);

    } else if (pixelRepresentation === 0 && bitsAllocated === 16) {

      // unsigned 16 bit
      frameOffset = pixelDataOffset + frameIndex * numPixels * 2;
      ePixelData = new Uint16Array(this._dataSet.byteArray.buffer, frameOffset, numPixels);

    } else if (pixelRepresentation === 1 && bitsAllocated === 16) {

      // signed 16 bit
      frameOffset = pixelDataOffset + frameIndex * numPixels * 2;
      ePixelData = new Int16Array(this._dataSet.byteArray.buffer, frameOffset, numPixels);

    }
  } else {
    // ASSUME RGB 8 BITS SIGNED!
    frameOffset = pixelDataOffset + frameIndex * numPixels;
    var encodedPixelData = new Uint8Array(this._dataSet.byteArray.buffer, frameOffset, numPixels);
    var photometricInterpretation = this.photometricInterpretation();

    if (photometricInterpretation === 'RGB') {
      // ALL GOOD, ALREADY ORDERED
      ePixelData = encodedPixelData;

    } else if (photometricInterpretation === 'YBR_FULL') {
      ePixelData = new Uint8Array(numPixels);
      // https://github.com/chafey/cornerstoneWADOImageLoader/blob/master/src/decodeYBRFull.js
      var nPixels = numPixels / 3;
      var ybrIndex = 0;
      var rgbaIndex = 0;
      for (var i = 0; i < nPixels; i++) {
        var y = encodedPixelData[ybrIndex++];
        var cb = encodedPixelData[ybrIndex++];
        var cr = encodedPixelData[ybrIndex++];
        ePixelData[rgbaIndex++] = y + 1.40200 * (cr - 128);// red
        ePixelData[rgbaIndex++] = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128); // green
        ePixelData[rgbaIndex++] = y + 1.77200 * (cb - 128); // blue
        ePixelData[rgbaIndex++] = 255; //alpha
      }
    } else {
      window.console.log('photometric interpolation not supported: ' + photometricInterpretation);
    }

  }

  return ePixelData;
};

VJS.parsers.dicom.prototype.minMaxPixelData =  function(pixelData) {

  var minMax = [65535, -32768];
  var numPixels = pixelData.length;
  for (var index = 0; index < numPixels; index++) {
    var spv = pixelData[index];

    //apply rescale/intercept
    // var rSlope = this.rescaleSlope(0);
    // var rIntercept = this.rescaleIntercept(0);
    // var rpv = spv * rSlope + rIntercept;

    // apply window/level
    // var wWidth = this.windowWidth(0);
    // var wCenter = this.windowCenter(0);
    // var wpv = rpv;
    // if( wpv < wCenter - wWidth/2){
    //   wpv = wCenter - wWidth/2;
    // }
    // else if( wpv > wCenter + wWidth/2){
    //   wpv = wCenter + wWidth/2;
    // }

    minMax[0] = Math.min(minMax[0], spv);
    minMax[1] = Math.max(minMax[1], spv);
  }

  return minMax;
};

// VJS.parsers.dicom.prototype.frameOfReferenceUID = function(imageJqueryDom) {
//   // try to access frame of reference UID through its DICOM tag
//   var seriesNumber = imageJqueryDom.find('[tag="00200052"] Value').text();

//   // if not available, assume we only have 1 frame
//   if (seriesNumber === '') {
//     seriesNumber = 1;
//   }
//   return seriesNumber;
// };

//
// ENDIAN NESS NOT TAKEN CARE OF
// http://stackoverflow.com/questions/5320439/how-do-i-swap-endian-ness-byte-order-of-a-variable-in-javascript
// http://www.barre.nom.fr/medical/samples/
//
//

/*** Exports ***/

var moduleType = typeof module;
if ((moduleType !== 'undefined') && module.exports) {
  module.exports = VJS.parsers.dicom;
}