/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2012 Mozilla Foundation
 *
 * 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.
 */
/* globals error, globalScope, warn */

'use strict';

var JpxImage = (function JpxImageClosure() {
  // Table E.1
  var SubbandsGainLog2 = {
    'LL': 0,
    'LH': 1,
    'HL': 1,
    'HH': 2
  };
  function JpxImage() {
    this.failOnCorruptedImage = false;
  }
  JpxImage.prototype = {
    load: function JpxImage_load(url) {
      var xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      xhr.responseType = 'arraybuffer';
      xhr.onload = (function() {
        // TODO catch parse error
        var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
        this.parse(data);
        if (this.onload)
          this.onload();
      }).bind(this);
      xhr.send(null);
    },
    parse: function JpxImage_parse(data) {
      function readUint(data, offset, bytes) {
        var n = 0;
        for (var i = 0; i < bytes; i++)
          n = n * 256 + (data[offset + i] & 0xFF);
        return n;
      }
      var position = 0, length = data.length;
      while (position < length) {
        var headerSize = 8;
        var lbox = readUint(data, position, 4);
        var tbox = readUint(data, position + 4, 4);
        position += headerSize;
        if (lbox == 1) {
          lbox = readUint(data, position, 8);
          position += 8;
          headerSize += 8;
        }
        if (lbox === 0)
          lbox = length - position + headerSize;
        if (lbox < headerSize)
          error('JPX error: Invalid box field size');
        var dataLength = lbox - headerSize;
        var jumpDataLength = true;
        switch (tbox) {
          case 0x6A501A1A: // 'jP\032\032'
            // TODO
            break;
          case 0x6A703268: // 'jp2h'
            jumpDataLength = false; // parsing child boxes
            break;
          case 0x636F6C72: // 'colr'
            // TODO
            break;
          case 0x6A703263: // 'jp2c'
            this.parseCodestream(data, position, position + dataLength);
            break;
        }
        if (jumpDataLength)
          position += dataLength;
      }
    },
    parseCodestream: function JpxImage_parseCodestream(data, start, end) {
      var context = {};
      try {
        var position = start;
        while (position < end) {
          var code = readUint16(data, position);
          position += 2;

          var length = 0, j;
          switch (code) {
            case 0xFF4F: // Start of codestream (SOC)
              context.mainHeader = true;
              break;
            case 0xFFD9: // End of codestream (EOC)
              break;
            case 0xFF51: // Image and tile size (SIZ)
              length = readUint16(data, position);
              var siz = {};
              siz.Xsiz = readUint32(data, position + 4);
              siz.Ysiz = readUint32(data, position + 8);
              siz.XOsiz = readUint32(data, position + 12);
              siz.YOsiz = readUint32(data, position + 16);
              siz.XTsiz = readUint32(data, position + 20);
              siz.YTsiz = readUint32(data, position + 24);
              siz.XTOsiz = readUint32(data, position + 28);
              siz.YTOsiz = readUint32(data, position + 32);
              var componentsCount = readUint16(data, position + 36);
              siz.Csiz = componentsCount;
              var components = [];
              j = position + 38;
              for (var i = 0; i < componentsCount; i++) {
                var component = {
                  precision: (data[j] & 0x7F) + 1,
                  isSigned: !!(data[j] & 0x80),
                  XRsiz: data[j + 1],
                  YRsiz: data[j + 1]
                };
                calculateComponentDimensions(component, siz);
                components.push(component);
              }
              context.SIZ = siz;
              context.components = components;
              calculateTileGrids(context, components);
              context.QCC = [];
              context.COC = [];
              break;
            case 0xFF5C: // Quantization default (QCD)
              length = readUint16(data, position);
              var qcd = {};
              j = position + 2;
              var sqcd = data[j++];
              var spqcdSize, scalarExpounded;
              switch (sqcd & 0x1F) {
                case 0:
                  spqcdSize = 8;
                  scalarExpounded = true;
                  break;
                case 1:
                  spqcdSize = 16;
                  scalarExpounded = false;
                  break;
                case 2:
                  spqcdSize = 16;
                  scalarExpounded = true;
                  break;
                default:
                  throw 'Invalid SQcd value ' + sqcd;
              }
              qcd.noQuantization = spqcdSize == 8;
              qcd.scalarExpounded = scalarExpounded;
              qcd.guardBits = sqcd >> 5;
              var spqcds = [];
              while (j < length + position) {
                var spqcd = {};
                if (spqcdSize == 8) {
                  spqcd.epsilon = data[j++] >> 3;
                  spqcd.mu = 0;
                } else {
                  spqcd.epsilon = data[j] >> 3;
                  spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
                  j += 2;
                }
                spqcds.push(spqcd);
              }
              qcd.SPqcds = spqcds;
              if (context.mainHeader)
                context.QCD = qcd;
              else {
                context.currentTile.QCD = qcd;
                context.currentTile.QCC = [];
              }
              break;
            case 0xFF5D: // Quantization component (QCC)
              length = readUint16(data, position);
              var qcc = {};
              j = position + 2;
              var cqcc;
              if (context.SIZ.Csiz < 257)
                cqcc = data[j++];
              else {
                cqcc = readUint16(data, j);
                j += 2;
              }
              var sqcd = data[j++];
              var spqcdSize, scalarExpounded;
              switch (sqcd & 0x1F) {
                case 0:
                  spqcdSize = 8;
                  scalarExpounded = true;
                  break;
                case 1:
                  spqcdSize = 16;
                  scalarExpounded = false;
                  break;
                case 2:
                  spqcdSize = 16;
                  scalarExpounded = true;
                  break;
                default:
                  throw 'Invalid SQcd value ' + sqcd;
              }
              qcc.noQuantization = spqcdSize == 8;
              qcc.scalarExpounded = scalarExpounded;
              qcc.guardBits = sqcd >> 5;
              var spqcds = [];
              while (j < length + position) {
                var spqcd = {};
                if (spqcdSize == 8) {
                  spqcd.epsilon = data[j++] >> 3;
                  spqcd.mu = 0;
                } else {
                  spqcd.epsilon = data[j] >> 3;
                  spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1];
                  j += 2;
                }
                spqcds.push(spqcd);
              }
              qcc.SPqcds = spqcds;
              if (context.mainHeader)
                context.QCC[cqcc] = qcc;
              else
                context.currentTile.QCC[cqcc] = qcc;
              break;
            case 0xFF52: // Coding style default (COD)
              length = readUint16(data, position);
              var cod = {};
              j = position + 2;
              var scod = data[j++];
              cod.entropyCoderWithCustomPrecincts = !!(scod & 1);
              cod.sopMarkerUsed = !!(scod & 2);
              cod.ephMarkerUsed = !!(scod & 4);
              var codingStyle = {};
              cod.progressionOrder = data[j++];
              cod.layersCount = readUint16(data, j);
              j += 2;
              cod.multipleComponentTransform = data[j++];

              cod.decompositionLevelsCount = data[j++];
              cod.xcb = (data[j++] & 0xF) + 2;
              cod.ycb = (data[j++] & 0xF) + 2;
              var blockStyle = data[j++];
              cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1);
              cod.resetContextProbabilities = !!(blockStyle & 2);
              cod.terminationOnEachCodingPass = !!(blockStyle & 4);
              cod.verticalyStripe = !!(blockStyle & 8);
              cod.predictableTermination = !!(blockStyle & 16);
              cod.segmentationSymbolUsed = !!(blockStyle & 32);
              cod.transformation = data[j++];
              if (cod.entropyCoderWithCustomPrecincts) {
                var precinctsSizes = {};
                while (j < length + position) {
                  var precinctsSize = data[j];
                  precinctsSizes.push({
                    PPx: precinctsSize & 0xF,
                    PPy: precinctsSize >> 4
                  });
                }
                cod.precinctsSizes = precinctsSizes;
              }

              if (cod.sopMarkerUsed || cod.ephMarkerUsed ||
                  cod.selectiveArithmeticCodingBypass ||
                  cod.resetContextProbabilities ||
                  cod.terminationOnEachCodingPass ||
                  cod.verticalyStripe || cod.predictableTermination)
                throw 'Unsupported COD options: ' +
                  globalScope.JSON.stringify(cod);

              if (context.mainHeader)
                context.COD = cod;
              else {
                context.currentTile.COD = cod;
                context.currentTile.COC = [];
              }
              break;
            case 0xFF90: // Start of tile-part (SOT)
              length = readUint16(data, position);
              var tile = {};
              tile.index = readUint16(data, position + 2);
              tile.length = readUint32(data, position + 4);
              tile.dataEnd = tile.length + position - 2;
              tile.partIndex = data[position + 8];
              tile.partsCount = data[position + 9];

              context.mainHeader = false;
              if (tile.partIndex === 0) {
                // reset component specific settings
                tile.COD = context.COD;
                tile.COC = context.COC.slice(0); // clone of the global COC
                tile.QCD = context.QCD;
                tile.QCC = context.QCC.slice(0); // clone of the global COC
              }
              context.currentTile = tile;
              break;
            case 0xFF93: // Start of data (SOD)
              var tile = context.currentTile;
              if (tile.partIndex === 0) {
                initializeTile(context, tile.index);
                buildPackets(context);
              }

              // moving to the end of the data
              length = tile.dataEnd - position;

              parseTilePackets(context, data, position, length);
              break;
            case 0xFF64: // Comment (COM)
              length = readUint16(data, position);
              // skipping content
              break;
            default:
              throw 'Unknown codestream code: ' + code.toString(16);
          }
          position += length;
        }
      } catch (e) {
        if (this.failOnCorruptedImage)
          error('JPX error: ' + e);
        else
          warn('JPX error: ' + e + '. Trying to recover');
      }
      this.tiles = transformComponents(context);
      this.width = context.SIZ.Xsiz - context.SIZ.XOsiz;
      this.height = context.SIZ.Ysiz - context.SIZ.YOsiz;
      this.componentsCount = context.SIZ.Csiz;
    }
  };
  function readUint32(data, offset) {
    return (data[offset] << 24) | (data[offset + 1] << 16) |
      (data[offset + 2] << 8) | data[offset + 3];
  }
  function readUint16(data, offset) {
    return (data[offset] << 8) | data[offset + 1];
  }
  function log2(x) {
    var n = 1, i = 0;
    while (x > n) {
      n <<= 1;
      i++;
    }
    return i;
  }
  function calculateComponentDimensions(component, siz) {
    // Section B.2 Component mapping
    component.x0 = Math.ceil(siz.XOsiz / component.XRsiz);
    component.x1 = Math.ceil(siz.Xsiz / component.XRsiz);
    component.y0 = Math.ceil(siz.YOsiz / component.YRsiz);
    component.y1 = Math.ceil(siz.Ysiz / component.YRsiz);
    component.width = component.x1 - component.x0;
    component.height = component.y1 - component.y0;
  }
  function calculateTileGrids(context, components) {
    var siz = context.SIZ;
    // Section B.3 Division into tile and tile-components
    var tiles = [];
    var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz);
    var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz);
    for (var q = 0; q < numYtiles; q++) {
      for (var p = 0; p < numXtiles; p++) {
        var tile = {};
        tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz);
        tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz);
        tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz);
        tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz);
        tile.width = tile.tx1 - tile.tx0;
        tile.height = tile.ty1 - tile.ty0;
        tile.components = [];
        tiles.push(tile);
      }
    }
    context.tiles = tiles;

    var componentsCount = siz.Csiz;
    for (var i = 0, ii = componentsCount; i < ii; i++) {
      var component = components[i];
      var tileComponents = [];
      for (var j = 0, jj = tiles.length; j < jj; j++) {
        var tileComponent = {}, tile = tiles[j];
        tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz);
        tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz);
        tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz);
        tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz);
        tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0;
        tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0;
        tile.components[i] = tileComponent;
      }
    }
  }
  function getBlocksDimensions(context, component, r) {
    var codOrCoc = component.codingStyleParameters;
    var result = {};
    if (!codOrCoc.entropyCoderWithCustomPrecincts) {
      result.PPx = 15;
      result.PPy = 15;
    } else {
      result.PPx = codOrCoc.precinctsSizes[r].PPx;
      result.PPy = codOrCoc.precinctsSizes[r].PPy;
    }
    // calculate codeblock size as described in section B.7
    result.xcb_ = r > 0 ? Math.min(codOrCoc.xcb, result.PPx - 1) :
      Math.min(codOrCoc.xcb, result.PPx);
    result.ycb_ = r > 0 ? Math.min(codOrCoc.ycb, result.PPy - 1) :
      Math.min(codOrCoc.ycb, result.PPy);
    return result;
  }
  function buildPrecincts(context, resolution, dimensions) {
    // Section B.6 Division resolution to precincts
    var precinctWidth = 1 << dimensions.PPx;
    var precinctHeight = 1 << dimensions.PPy;
    var numprecinctswide = resolution.trx1 > resolution.trx0 ?
      Math.ceil(resolution.trx1 / precinctWidth) -
      Math.floor(resolution.trx0 / precinctWidth) : 0;
    var numprecinctshigh = resolution.try1 > resolution.try0 ?
      Math.ceil(resolution.try1 / precinctHeight) -
      Math.floor(resolution.try0 / precinctHeight) : 0;
    var numprecincts = numprecinctswide * numprecinctshigh;
    var precinctXOffset = Math.floor(resolution.trx0 / precinctWidth) *
      precinctWidth;
    var precinctYOffset = Math.floor(resolution.try0 / precinctHeight) *
      precinctHeight;
    resolution.precinctParameters = {
      precinctXOffset: precinctXOffset,
      precinctYOffset: precinctYOffset,
      precinctWidth: precinctWidth,
      precinctHeight: precinctHeight,
      numprecinctswide: numprecinctswide,
      numprecinctshigh: numprecinctshigh,
      numprecincts: numprecincts
    };
  }
  function buildCodeblocks(context, subband, dimensions) {
    // Section B.7 Division sub-band into code-blocks
    var xcb_ = dimensions.xcb_;
    var ycb_ = dimensions.ycb_;
    var codeblockWidth = 1 << xcb_;
    var codeblockHeight = 1 << ycb_;
    var cbx0 = Math.floor(subband.tbx0 / codeblockWidth);
    var cby0 = Math.floor(subband.tby0 / codeblockHeight);
    var cbx1 = Math.ceil(subband.tbx1 / codeblockWidth);
    var cby1 = Math.ceil(subband.tby1 / codeblockHeight);
    var precinctParameters = subband.resolution.precinctParameters;
    var codeblocks = [];
    var precincts = [];
    for (var j = cby0; j < cby1; j++) {
      for (var i = cbx0; i < cbx1; i++) {
        var codeblock = {
          cbx: i,
          cby: j,
          tbx0: codeblockWidth * i,
          tby0: codeblockHeight * j,
          tbx1: codeblockWidth * (i + 1),
          tby1: codeblockHeight * (j + 1)
        };
        // calculate precinct number
        var pi = Math.floor((codeblock.tbx0 -
          precinctParameters.precinctXOffset) /
          precinctParameters.precinctWidth);
        var pj = Math.floor((codeblock.tby0 -
          precinctParameters.precinctYOffset) /
          precinctParameters.precinctHeight);
        var precinctNumber = pj +
          pi * precinctParameters.numprecinctswide;
        codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0);
        codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0);
        codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1);
        codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1);
        codeblock.precinctNumber = precinctNumber;
        codeblock.subbandType = subband.type;
        var coefficientsLength = (codeblock.tbx1_ - codeblock.tbx0_) *
          (codeblock.tby1_ - codeblock.tby0_);
        codeblock.Lblock = 3;
        codeblocks.push(codeblock);
        // building precinct for the sub-band
        var precinct;
        if (precinctNumber in precincts) {
          precinct = precincts[precinctNumber];
          precinct.cbxMin = Math.min(precinct.cbxMin, i);
          precinct.cbyMin = Math.min(precinct.cbyMin, j);
          precinct.cbxMax = Math.max(precinct.cbxMax, i);
          precinct.cbyMax = Math.max(precinct.cbyMax, j);
        } else {
          precincts[precinctNumber] = precinct = {
            cbxMin: i,
            cbyMin: j,
            cbxMax: i,
            cbyMax: j
          };
        }
        codeblock.precinct = precinct;
      }
    }
    subband.codeblockParameters = {
      codeblockWidth: xcb_,
      codeblockHeight: ycb_,
      numcodeblockwide: cbx1 - cbx0 + 1,
      numcodeblockhigh: cby1 - cby1 + 1
    };
    subband.codeblocks = codeblocks;
    for (var i = 0, ii = codeblocks.length; i < ii; i++) {
      var codeblock = codeblocks[i];
      var precinctNumber = codeblock.precinctNumber;
    }
    subband.precincts = precincts;
  }
  function createPacket(resolution, precinctNumber, layerNumber) {
    var precinctCodeblocks = [];
    // Section B.10.8 Order of info in packet
    var subbands = resolution.subbands;
    // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence
    for (var i = 0, ii = subbands.length; i < ii; i++) {
      var subband = subbands[i];
      var codeblocks = subband.codeblocks;
      for (var j = 0, jj = codeblocks.length; j < jj; j++) {
        var codeblock = codeblocks[j];
        if (codeblock.precinctNumber != precinctNumber)
          continue;
        precinctCodeblocks.push(codeblock);
      }
    }
    return {
      layerNumber: layerNumber,
      codeblocks: precinctCodeblocks
    };
  }
  function LayerResolutionComponentPositionIterator(context) {
    var siz = context.SIZ;
    var tileIndex = context.currentTile.index;
    var tile = context.tiles[tileIndex];
    var layersCount = tile.codingStyleDefaultParameters.layersCount;
    var componentsCount = siz.Csiz;
    var maxDecompositionLevelsCount = 0;
    for (var q = 0; q < componentsCount; q++) {
      maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
        tile.components[q].codingStyleParameters.decompositionLevelsCount);
    }

    var l = 0, r = 0, i = 0, k = 0;

    this.nextPacket = function JpxImage_nextPacket() {
      // Section B.12.1.1 Layer-resolution-component-position
      for (; l < layersCount; l++) {
        for (; r <= maxDecompositionLevelsCount; r++) {
          for (; i < componentsCount; i++) {
            var component = tile.components[i];
            if (r > component.codingStyleParameters.decompositionLevelsCount)
              continue;

            var resolution = component.resolutions[r];
            var numprecincts = resolution.precinctParameters.numprecincts;
            for (; k < numprecincts;) {
              var packet = createPacket(resolution, k, l);
              k++;
              return packet;
            }
            k = 0;
          }
          i = 0;
        }
        r = 0;
      }
      throw 'Out of packets';
    };
  }
  function ResolutionLayerComponentPositionIterator(context) {
    var siz = context.SIZ;
    var tileIndex = context.currentTile.index;
    var tile = context.tiles[tileIndex];
    var layersCount = tile.codingStyleDefaultParameters.layersCount;
    var componentsCount = siz.Csiz;
    var maxDecompositionLevelsCount = 0;
    for (var q = 0; q < componentsCount; q++) {
      maxDecompositionLevelsCount = Math.max(maxDecompositionLevelsCount,
        tile.components[q].codingStyleParameters.decompositionLevelsCount);
    }

    var r = 0, l = 0, i = 0, k = 0;

    this.nextPacket = function JpxImage_nextPacket() {
      // Section B.12.1.2 Resolution-layer-component-position
      for (; r <= maxDecompositionLevelsCount; r++) {
        for (; l < layersCount; l++) {
          for (; i < componentsCount; i++) {
            var component = tile.components[i];
            if (r > component.codingStyleParameters.decompositionLevelsCount)
              continue;

            var resolution = component.resolutions[r];
            var numprecincts = resolution.precinctParameters.numprecincts;
            for (; k < numprecincts;) {
              var packet = createPacket(resolution, k, l);
              k++;
              return packet;
            }
            k = 0;
          }
          i = 0;
        }
        l = 0;
      }
      throw 'Out of packets';
    };
  }
  function buildPackets(context) {
    var siz = context.SIZ;
    var tileIndex = context.currentTile.index;
    var tile = context.tiles[tileIndex];
    var componentsCount = siz.Csiz;
    // Creating resolutions and sub-bands for each component
    for (var c = 0; c < componentsCount; c++) {
      var component = tile.components[c];
      var decompositionLevelsCount =
        component.codingStyleParameters.decompositionLevelsCount;
      // Section B.5 Resolution levels and sub-bands
      var resolutions = [];
      var subbands = [];
      for (var r = 0; r <= decompositionLevelsCount; r++) {
        var blocksDimensions = getBlocksDimensions(context, component, r);
        var resolution = {};
        var scale = 1 << (decompositionLevelsCount - r);
        resolution.trx0 = Math.ceil(component.tcx0 / scale);
        resolution.try0 = Math.ceil(component.tcy0 / scale);
        resolution.trx1 = Math.ceil(component.tcx1 / scale);
        resolution.try1 = Math.ceil(component.tcy1 / scale);
        buildPrecincts(context, resolution, blocksDimensions);
        resolutions.push(resolution);

        var subband;
        if (r === 0) {
          // one sub-band (LL) with last decomposition
          subband = {};
          subband.type = 'LL';
          subband.tbx0 = Math.ceil(component.tcx0 / scale);
          subband.tby0 = Math.ceil(component.tcy0 / scale);
          subband.tbx1 = Math.ceil(component.tcx1 / scale);
          subband.tby1 = Math.ceil(component.tcy1 / scale);
          subband.resolution = resolution;
          buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolution.subbands = [subband];
        } else {
          var bscale = 1 << (decompositionLevelsCount - r + 1);
          var resolutionSubbands = [];
          // three sub-bands (HL, LH and HH) with rest of decompositions
          subband = {};
          subband.type = 'HL';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
          subband.tby0 = Math.ceil(component.tcy0 / bscale);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
          subband.tby1 = Math.ceil(component.tcy1 / bscale);
          subband.resolution = resolution;
          buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          subband = {};
          subband.type = 'LH';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale);
          subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale);
          subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
          subband.resolution = resolution;
          buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          subband = {};
          subband.type = 'HH';
          subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5);
          subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5);
          subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5);
          subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5);
          subband.resolution = resolution;
          buildCodeblocks(context, subband, blocksDimensions);
          subbands.push(subband);
          resolutionSubbands.push(subband);

          resolution.subbands = resolutionSubbands;
        }
      }
      component.resolutions = resolutions;
      component.subbands = subbands;
    }
    // Generate the packets sequence
    var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder;
    var packetsIterator;
    switch (progressionOrder) {
      case 0:
        tile.packetsIterator =
          new LayerResolutionComponentPositionIterator(context);
        break;
      case 1:
        tile.packetsIterator =
          new ResolutionLayerComponentPositionIterator(context);
        break;
      default:
        throw 'Unsupported progression order ' + progressionOrder;
    }
  }
  function parseTilePackets(context, data, offset, dataLength) {
    var position = 0;
    var buffer, bufferSize = 0, skipNextBit = false;
    function readBits(count) {
      while (bufferSize < count) {
        var b = data[offset + position];
        position++;
        if (skipNextBit) {
          buffer = (buffer << 7) | b;
          bufferSize += 7;
          skipNextBit = false;
        } else {
          buffer = (buffer << 8) | b;
          bufferSize += 8;
        }
        if (b == 0xFF) {
          skipNextBit = true;
        }
      }
      bufferSize -= count;
      return (buffer >>> bufferSize) & ((1 << count) - 1);
    }
    function alignToByte() {
      bufferSize = 0;
      if (skipNextBit) {
        position++;
        skipNextBit = false;
      }
    }
    function readCodingpasses() {
      var value = readBits(1);
      if (value === 0)
        return 1;
      value = (value << 1) | readBits(1);
      if (value == 0x02)
        return 2;
      value = (value << 2) | readBits(2);
      if (value <= 0x0E)
        return (value & 0x03) + 3;
      value = (value << 5) | readBits(5);
      if (value <= 0x1FE)
        return (value & 0x1F) + 6;
      value = (value << 7) | readBits(7);
      return (value & 0x7F) + 37;
    }
    var tileIndex = context.currentTile.index;
    var tile = context.tiles[tileIndex];
    var packetsIterator = tile.packetsIterator;
    while (position < dataLength) {
      var packet = packetsIterator.nextPacket();
      if (!readBits(1)) {
        alignToByte();
        continue;
      }
      var layerNumber = packet.layerNumber;
      var queue = [];
      for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) {
        var codeblock = packet.codeblocks[i];
        var precinct = codeblock.precinct;
        var codeblockColumn = codeblock.cbx - precinct.cbxMin;
        var codeblockRow = codeblock.cby - precinct.cbyMin;
        var codeblockIncluded = false;
        var firstTimeInclusion = false;
        if ('included' in codeblock) {
          codeblockIncluded = !!readBits(1);
        } else {
          // reading inclusion tree
          var precinct = codeblock.precinct;
          var inclusionTree, zeroBitPlanesTree;
          if ('inclusionTree' in precinct) {
            inclusionTree = precinct.inclusionTree;
          } else {
            // building inclusion and zero bit-planes trees
            var width = precinct.cbxMax - precinct.cbxMin + 1;
            var height = precinct.cbyMax - precinct.cbyMin + 1;
            inclusionTree = new InclusionTree(width, height, layerNumber);
            zeroBitPlanesTree = new TagTree(width, height);
            precinct.inclusionTree = inclusionTree;
            precinct.zeroBitPlanesTree = zeroBitPlanesTree;
          }

          if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
            while (true) {
              if (readBits(1)) {
                var valueReady = !inclusionTree.nextLevel();
                if (valueReady) {
                  codeblock.included = true;
                  codeblockIncluded = firstTimeInclusion = true;
                  break;
                }
              } else {
                inclusionTree.incrementValue(layerNumber);
                break;
              }
            }
          }
        }
        if (!codeblockIncluded)
          continue;
        if (firstTimeInclusion) {
          zeroBitPlanesTree = precinct.zeroBitPlanesTree;
          zeroBitPlanesTree.reset(codeblockColumn, codeblockRow);
          while (true) {
            if (readBits(1)) {
              var valueReady = !zeroBitPlanesTree.nextLevel();
              if (valueReady)
                break;
            } else
              zeroBitPlanesTree.incrementValue();
          }
          codeblock.zeroBitPlanes = zeroBitPlanesTree.value;
        }
        var codingpasses = readCodingpasses();
        while (readBits(1))
          codeblock.Lblock++;
        var codingpassesLog2 = log2(codingpasses);
        // rounding down log2
        var bits = ((codingpasses < (1 << codingpassesLog2)) ?
          codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock;
        var codedDataLength = readBits(bits);
        queue.push({
          codeblock: codeblock,
          codingpasses: codingpasses,
          dataLength: codedDataLength
        });
      }
      alignToByte();
      while (queue.length > 0) {
        var packetItem = queue.shift();
        var codeblock = packetItem.codeblock;
        if (!('data' in codeblock))
          codeblock.data = [];
        codeblock.data.push({
          data: data,
          start: offset + position,
          end: offset + position + packetItem.dataLength,
          codingpasses: packetItem.codingpasses
        });
        position += packetItem.dataLength;
      }
    }
    return position;
  }
  function copyCoefficients(coefficients, x0, y0, width, height,
                            delta, mb, codeblocks, transformation,
                            segmentationSymbolUsed) {
    var r = 0.5; // formula (E-6)
    for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
      var codeblock = codeblocks[i];
      var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
      var blockHeight = codeblock.tby1_ - codeblock.tby0_;
      if (blockWidth === 0 || blockHeight === 0)
        continue;
      if (!('data' in codeblock))
        continue;

      var bitModel, currentCodingpassType;
      bitModel = new BitModel(blockWidth, blockHeight, codeblock.subbandType,
        codeblock.zeroBitPlanes);
      currentCodingpassType = 2; // first bit plane starts from cleanup

      // collect data
      var data = codeblock.data, totalLength = 0, codingpasses = 0;
      for (var q = 0, qq = data.length; q < qq; q++) {
        var dataItem = data[q];
        totalLength += dataItem.end - dataItem.start;
        codingpasses += dataItem.codingpasses;
      }
      var encodedData = new Uint8Array(totalLength), k = 0;
      for (var q = 0, qq = data.length; q < qq; q++) {
        var dataItem = data[q];
        var chunk = dataItem.data.subarray(dataItem.start, dataItem.end);
        encodedData.set(chunk, k);
        k += chunk.length;
      }
      // decoding the item
      var decoder = new ArithmeticDecoder(encodedData, 0, totalLength);
      bitModel.setDecoder(decoder);

      for (var q = 0; q < codingpasses; q++) {
        switch (currentCodingpassType) {
          case 0:
            bitModel.runSignificancePropogationPass();
            break;
          case 1:
            bitModel.runMagnitudeRefinementPass();
            break;
          case 2:
            bitModel.runCleanupPass();
            if (segmentationSymbolUsed)
              bitModel.checkSegmentationSymbol();
            break;
        }
        currentCodingpassType = (currentCodingpassType + 1) % 3;
      }

      var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
      var position = 0;
      for (var j = 0; j < blockHeight; j++) {
        for (var k = 0; k < blockWidth; k++) {
          var n = (bitModel.coefficentsSign[position] ? -1 : 1) *
            bitModel.coefficentsMagnitude[position];
          var nb = bitModel.bitsDecoded[position], correction;
          if (transformation === 0 || mb > nb) {
            // use r only if transformation is irreversible or
            // not all bitplanes were decoded for reversible transformation
            n += n < 0 ? n - r : n > 0 ? n + r : 0;
            correction = 1 << (mb - nb);
          } else
            correction = 1;
          coefficients[offset++] = n * correction * delta;
          position++;
        }
        offset += width - blockWidth;
      }
    }
  }
  function transformTile(context, tile, c) {
    var component = tile.components[c];
    var codingStyleParameters = component.codingStyleParameters;
    var quantizationParameters = component.quantizationParameters;
    var decompositionLevelsCount =
      codingStyleParameters.decompositionLevelsCount;
    var spqcds = quantizationParameters.SPqcds;
    var scalarExpounded = quantizationParameters.scalarExpounded;
    var guardBits = quantizationParameters.guardBits;
    var transformation = codingStyleParameters.transformation;
    var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed;
    var precision = context.components[c].precision;

    var subbandCoefficients = [];
    var k = 0, b = 0;
    for (var i = 0; i <= decompositionLevelsCount; i++) {
      var resolution = component.resolutions[i];

      for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
        var mu, epsilon;
        if (!scalarExpounded) {
          // formula E-5
          mu = spqcds[0].mu;
          epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0);
        } else {
          mu = spqcds[b].mu;
          epsilon = spqcds[b].epsilon;
        }

        var subband = resolution.subbands[j];
        var width = subband.tbx1 - subband.tbx0;
        var height = subband.tby1 - subband.tby0;
        var gainLog2 = SubbandsGainLog2[subband.type];

        // calulate quantization coefficient (Section E.1.1.1)
        var delta = Math.pow(2, (precision + gainLog2) - epsilon) *
          (1 + mu / 2048);
        var mb = (guardBits + epsilon - 1);

        var coefficients = new Float32Array(width * height);
        copyCoefficients(coefficients, subband.tbx0, subband.tby0,
          width, height, delta, mb, subband.codeblocks, transformation,
          segmentationSymbolUsed);

        subbandCoefficients.push({
          width: width,
          height: height,
          items: coefficients
        });

        b++;
      }
    }

    var transformation = codingStyleParameters.transformation;
    var transform = transformation === 0 ? new IrreversibleTransform() :
      new ReversibleTransform();
    var result = transform.calculate(subbandCoefficients,
      component.tcx0, component.tcy0);
    return {
      left: component.tcx0,
      top: component.tcy0,
      width: result.width,
      height: result.height,
      items: result.items
    };
  }
  function transformComponents(context) {
    var siz = context.SIZ;
    var components = context.components;
    var componentsCount = siz.Csiz;
    var resultImages = [];
    for (var i = 0, ii = context.tiles.length; i < ii; i++) {
      var tile = context.tiles[i];
      var result = [];
      for (var c = 0; c < componentsCount; c++) {
        var image = transformTile(context, tile, c);
        result.push(image);
      }

      // Section G.2.2 Inverse multi component transform
      if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
        var y0items = result[0].items;
        var y1items = result[1].items;
        var y2items = result[2].items;
        for (var j = 0, jj = y0items.length; j < jj; j++) {
          var y0 = y0items[j], y1 = y1items[j], y2 = y2items[j];
          var i1 = y0 - ((y2 + y1) >> 2);
          y1items[j] = i1;
          y0items[j] = y2 + i1;
          y2items[j] = y1 + i1;
        }
      }

      // Section G.1 DC level shifting to unsigned component values
      for (var c = 0; c < componentsCount; c++) {
        var component = components[c];
        if (component.isSigned)
          continue;

        var offset = 1 << (component.precision - 1);
        var tileImage = result[c];
        var items = tileImage.items;
        for (var j = 0, jj = items.length; j < jj; j++)
          items[j] += offset;
      }

      // To simplify things: shift and clamp output to 8 bit unsigned
      for (var c = 0; c < componentsCount; c++) {
        var component = components[c];
        var offset = component.isSigned ? 128 : 0;
        var shift = component.precision - 8;
        var tileImage = result[c];
        var items = tileImage.items;
        var data = new Uint8Array(items.length);
        for (var j = 0, jj = items.length; j < jj; j++) {
          var value = (items[j] >> shift) + offset;
          data[j] = value < 0 ? 0 : value > 255 ? 255 : value;
        }
        result[c].items = data;
      }

      resultImages.push(result);
    }
    return resultImages;
  }
  function initializeTile(context, tileIndex) {
    var siz = context.SIZ;
    var componentsCount = siz.Csiz;
    var tile = context.tiles[tileIndex];
    var resultTiles = [];
    for (var c = 0; c < componentsCount; c++) {
      var component = tile.components[c];
      var qcdOrQcc = c in context.currentTile.QCC ?
        context.currentTile.QCC[c] : context.currentTile.QCD;
      component.quantizationParameters = qcdOrQcc;
      var codOrCoc = c in context.currentTile.COC ?
        context.currentTile.COC[c] : context.currentTile.COD;
      component.codingStyleParameters = codOrCoc;
    }
    tile.codingStyleDefaultParameters = context.currentTile.COD;
  }

  // Section B.10.2 Tag trees
  var TagTree = (function TagTreeClosure() {
    function TagTree(width, height) {
      var levelsLength = log2(Math.max(width, height)) + 1;
      this.levels = [];
      for (var i = 0; i < levelsLength; i++) {
        var level = {
          width: width,
          height: height,
          items: []
        };
        this.levels.push(level);
        width = Math.ceil(width / 2);
        height = Math.ceil(height / 2);
      }
    }
    TagTree.prototype = {
      reset: function TagTree_reset(i, j) {
        var currentLevel = 0, value = 0;
        while (currentLevel < this.levels.length) {
          var level = this.levels[currentLevel];
          var index = i + j * level.width;
          if (index in level.items) {
            value = level.items[index];
            break;
          }
          level.index = index;
          i >>= 1;
          j >>= 1;
          currentLevel++;
        }
        currentLevel--;
        var level = this.levels[currentLevel];
        level.items[level.index] = value;
        this.currentLevel = currentLevel;
        delete this.value;
      },
      incrementValue: function TagTree_incrementValue() {
        var level = this.levels[this.currentLevel];
        level.items[level.index]++;
      },
      nextLevel: function TagTree_nextLevel() {
        var currentLevel = this.currentLevel;
        var level = this.levels[currentLevel];
        var value = level.items[level.index];
        currentLevel--;
        if (currentLevel < 0) {
          this.value = value;
          return false;
        }

        this.currentLevel = currentLevel;
        var level = this.levels[currentLevel];
        level.items[level.index] = value;
        return true;
      }
    };
    return TagTree;
  })();

  var InclusionTree = (function InclusionTreeClosure() {
    function InclusionTree(width, height,  defaultValue) {
      var levelsLength = log2(Math.max(width, height)) + 1;
      this.levels = [];
      for (var i = 0; i < levelsLength; i++) {
        var items = new Uint8Array(width * height);
        for (var j = 0, jj = items.length; j < jj; j++)
          items[j] = defaultValue;

        var level = {
          width: width,
          height: height,
          items: items
        };
        this.levels.push(level);

        width = Math.ceil(width / 2);
        height = Math.ceil(height / 2);
      }
    }
    InclusionTree.prototype = {
      reset: function InclusionTree_reset(i, j, stopValue) {
        var currentLevel = 0;
        while (currentLevel < this.levels.length) {
          var level = this.levels[currentLevel];
          var index = i + j * level.width;
          level.index = index;
          var value = level.items[index];

          if (value == 0xFF)
            break;

          if (value > stopValue) {
            this.currentLevel = currentLevel;
            // already know about this one, propagating the value to top levels
            this.propagateValues();
            return false;
          }

          i >>= 1;
          j >>= 1;
          currentLevel++;
        }
        this.currentLevel = currentLevel - 1;
        return true;
      },
      incrementValue: function InclusionTree_incrementValue(stopValue) {
        var level = this.levels[this.currentLevel];
        level.items[level.index] = stopValue + 1;
        this.propagateValues();
      },
      propagateValues: function InclusionTree_propagateValues() {
        var levelIndex = this.currentLevel;
        var level = this.levels[levelIndex];
        var currentValue = level.items[level.index];
        while (--levelIndex >= 0) {
          var level = this.levels[levelIndex];
          level.items[level.index] = currentValue;
        }
      },
      nextLevel: function InclusionTree_nextLevel() {
        var currentLevel = this.currentLevel;
        var level = this.levels[currentLevel];
        var value = level.items[level.index];
        level.items[level.index] = 0xFF;
        currentLevel--;
        if (currentLevel < 0)
          return false;

        this.currentLevel = currentLevel;
        var level = this.levels[currentLevel];
        level.items[level.index] = value;
        return true;
      }
    };
    return InclusionTree;
  })();

  // Implements C.3. Arithmetic decoding procedures
  var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
    var QeTable = [
      {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
      {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
      {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
      {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
      {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
      {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
      {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
      {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
      {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
      {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
      {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
      {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
      {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
      {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
      {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
      {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
      {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
      {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
      {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
      {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
      {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
      {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
      {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
      {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
      {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
      {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
      {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
      {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
      {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
      {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
      {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
      {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
      {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
      {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
      {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
      {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
      {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
      {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
      {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
      {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
      {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
      {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
      {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
      {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
      {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
      {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
      {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
    ];

    function ArithmeticDecoder(data, start, end) {
      this.data = data;
      this.bp = start;
      this.dataEnd = end;

      this.chigh = data[start];
      this.clow = 0;

      this.byteIn();

      this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
      this.clow = (this.clow << 7) & 0xFFFF;
      this.ct -= 7;
      this.a = 0x8000;
    }

    ArithmeticDecoder.prototype = {
      byteIn: function ArithmeticDecoder_byteIn() {
        var data = this.data;
        var bp = this.bp;
        if (data[bp] == 0xFF) {
          var b1 = data[bp + 1];
          if (b1 > 0x8F) {
            this.clow += 0xFF00;
            this.ct = 8;
          } else {
            bp++;
            this.clow += (data[bp] << 9);
            this.ct = 7;
            this.bp = bp;
          }
        } else {
          bp++;
          this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
          this.ct = 8;
          this.bp = bp;
        }
        if (this.clow > 0xFFFF) {
          this.chigh += (this.clow >> 16);
          this.clow &= 0xFFFF;
        }
      },
      readBit: function ArithmeticDecoder_readBit(cx) {
        var qeIcx = QeTable[cx.index].qe;
        this.a -= qeIcx;

        if (this.chigh < qeIcx) {
          var d = this.exchangeLps(cx);
          this.renormD();
          return d;
        } else {
          this.chigh -= qeIcx;
          if ((this.a & 0x8000) === 0) {
            var d = this.exchangeMps(cx);
            this.renormD();
            return d;
          } else {
            return cx.mps;
          }
        }
      },
      renormD: function ArithmeticDecoder_renormD() {
        do {
          if (this.ct === 0)
            this.byteIn();

          this.a <<= 1;
          this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
          this.clow = (this.clow << 1) & 0xFFFF;
          this.ct--;
        } while ((this.a & 0x8000) === 0);
      },
      exchangeMps: function ArithmeticDecoder_exchangeMps(cx) {
        var d;
        var qeTableIcx = QeTable[cx.index];
        if (this.a < qeTableIcx.qe) {
          d = 1 - cx.mps;

          if (qeTableIcx.switchFlag == 1) {
            cx.mps = 1 - cx.mps;
          }
          cx.index = qeTableIcx.nlps;
        } else {
          d = cx.mps;
          cx.index = qeTableIcx.nmps;
        }
        return d;
      },
      exchangeLps: function ArithmeticDecoder_exchangeLps(cx) {
        var d;
        var qeTableIcx = QeTable[cx.index];
        if (this.a < qeTableIcx.qe) {
          this.a = qeTableIcx.qe;
          d = cx.mps;
          cx.index = qeTableIcx.nmps;
        } else {
          this.a = qeTableIcx.qe;
          d = 1 - cx.mps;

          if (qeTableIcx.switchFlag == 1) {
            cx.mps = 1 - cx.mps;
          }
          cx.index = qeTableIcx.nlps;
        }
        return d;
      }
    };

    return ArithmeticDecoder;
  })();

  // Section D. Coefficient bit modeling
  var BitModel = (function BitModelClosure() {
    // Table D-1
    // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4),
    // vv - sum of Vi (0..2), and hh - sum of Hi (0..2)
    var LLAndLHContextsLabel = new Uint8Array([
      0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4,
      7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6,
      8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8
    ]);
    var HLContextLabel = new Uint8Array([
      0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8,
      8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3,
      4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8
    ]);
    var HHContextLabel = new Uint8Array([
      0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5,
      5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8,
      8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8
    ]);

    // Table D-2
    function calcSignContribution(significance0, sign0, significance1, sign1) {
      if (significance1) {
        if (!sign1)
          return significance0 ? (!sign0 ? 1 : 0) : 1;
        else
          return significance0 ? (!sign0 ? 0 : -1) : -1;
      } else
        return significance0 ? (!sign0 ? 1 : -1) : 0;
    }
    // Table D-3
    var SignContextLabels = [
      {contextLabel: 13, xorBit: 0},
      {contextLabel: 12, xorBit: 0},
      {contextLabel: 11, xorBit: 0},
      {contextLabel: 10, xorBit: 0},
      {contextLabel: 9, xorBit: 0},
      {contextLabel: 10, xorBit: 1},
      {contextLabel: 11, xorBit: 1},
      {contextLabel: 12, xorBit: 1},
      {contextLabel: 13, xorBit: 1}
    ];

    function BitModel(width, height, subband, zeroBitPlanes) {
      this.width = width;
      this.height = height;

      this.contextLabelTable = subband == 'HH' ? HHContextLabel :
        subband == 'HL' ? HLContextLabel : LLAndLHContextsLabel;

      var coefficientCount = width * height;

      // coefficients outside the encoding region treated as insignificant
      // add border state cells for significanceState
      this.neighborsSignificance = new Uint8Array(coefficientCount);
      this.coefficentsSign = new Uint8Array(coefficientCount);
      this.coefficentsMagnitude = new Uint32Array(coefficientCount);
      this.processingFlags = new Uint8Array(coefficientCount);

      var bitsDecoded = new Uint8Array(this.width * this.height);
      for (var i = 0, ii = bitsDecoded.length; i < ii; i++)
        bitsDecoded[i] = zeroBitPlanes;
      this.bitsDecoded = bitsDecoded;

      this.reset();
    }

    BitModel.prototype = {
      setDecoder: function BitModel_setDecoder(decoder) {
        this.decoder = decoder;
      },
      reset: function BitModel_reset() {
        this.uniformContext = {index: 46, mps: 0};
        this.runLengthContext = {index: 3, mps: 0};
        this.contexts = [];
        this.contexts.push({index: 4, mps: 0});
        for (var i = 1; i <= 16; i++)
          this.contexts.push({index: 0, mps: 0});
      },
      setNeighborsSignificance:
        function BitModel_setNeighborsSignificance(row, column) {
        var neighborsSignificance = this.neighborsSignificance;
        var width = this.width, height = this.height;
        var index = row * width + column;
        if (row > 0) {
          if (column > 0)
            neighborsSignificance[index - width - 1] += 0x10;
          if (column + 1 < width)
            neighborsSignificance[index - width + 1] += 0x10;
          neighborsSignificance[index - width] += 0x04;
        }
        if (row + 1 < height) {
          if (column > 0)
            neighborsSignificance[index + width - 1] += 0x10;
          if (column + 1 < width)
            neighborsSignificance[index + width + 1] += 0x10;
          neighborsSignificance[index + width] += 0x04;
        }
        if (column > 0)
          neighborsSignificance[index - 1] += 0x01;
        if (column + 1 < width)
          neighborsSignificance[index + 1] += 0x01;
        neighborsSignificance[index] |= 0x80;
      },
      runSignificancePropogationPass:
        function BitModel_runSignificancePropogationPass() {
        var decoder = this.decoder;
        var width = this.width, height = this.height;
        var coefficentsMagnitude = this.coefficentsMagnitude;
        var coefficentsSign = this.coefficentsSign;
        var contextLabels = this.contextLabels;
        var neighborsSignificance = this.neighborsSignificance;
        var processingFlags = this.processingFlags;
        var contexts = this.contexts;
        var labels = this.contextLabelTable;
        var bitsDecoded = this.bitsDecoded;
        // clear processed flag
        var processedInverseMask = ~1;
        var processedMask = 1;
        var firstMagnitudeBitMask = 2;
        for (var q = 0, qq = width * height; q < qq; q++)
          processingFlags[q] &= processedInverseMask;

        for (var i0 = 0; i0 < height; i0 += 4) {
          for (var j = 0; j < width; j++) {
            var index = i0 * width + j;
            for (var i1 = 0; i1 < 4; i1++, index += width) {
              var i = i0 + i1;
              if (i >= height)
                break;

              if (coefficentsMagnitude[index] || !neighborsSignificance[index])
                continue;

              var contextLabel = labels[neighborsSignificance[index]];
              var cx = contexts[contextLabel];
              var decision = decoder.readBit(cx);
              if (decision) {
                var sign = this.decodeSignBit(i, j);
                coefficentsSign[index] = sign;
                coefficentsMagnitude[index] = 1;
                this.setNeighborsSignificance(i, j);
                processingFlags[index] |= firstMagnitudeBitMask;
              }
              bitsDecoded[index]++;
              processingFlags[index] |= processedMask;
            }
          }
        }
      },
      decodeSignBit: function BitModel_decodeSignBit(row, column) {
        var width = this.width, height = this.height;
        var index = row * width + column;
        var coefficentsMagnitude = this.coefficentsMagnitude;
        var coefficentsSign = this.coefficentsSign;
        var horizontalContribution = calcSignContribution(
          column > 0 && coefficentsMagnitude[index - 1],
          coefficentsSign[index - 1],
          column + 1 < width && coefficentsMagnitude[index + 1],
          coefficentsSign[index + 1]);
        var verticalContribution = calcSignContribution(
          row > 0 && coefficentsMagnitude[index - width],
          coefficentsSign[index - width],
          row + 1 < height && coefficentsMagnitude[index + width],
          coefficentsSign[index + width]);

        var contextLabelAndXor = SignContextLabels[
          3 * (1 - horizontalContribution) + (1 - verticalContribution)];
        var contextLabel = contextLabelAndXor.contextLabel;
        var cx = this.contexts[contextLabel];
        var decoded = this.decoder.readBit(cx);
        return decoded ^ contextLabelAndXor.xorBit;
      },
      runMagnitudeRefinementPass:
        function BitModel_runMagnitudeRefinementPass() {
        var decoder = this.decoder;
        var width = this.width, height = this.height;
        var coefficentsMagnitude = this.coefficentsMagnitude;
        var neighborsSignificance = this.neighborsSignificance;
        var contexts = this.contexts;
        var bitsDecoded = this.bitsDecoded;
        var processingFlags = this.processingFlags;
        var processedMask = 1;
        var firstMagnitudeBitMask = 2;
        for (var i0 = 0; i0 < height; i0 += 4) {
          for (var j = 0; j < width; j++) {
            for (var i1 = 0; i1 < 4; i1++) {
              var i = i0 + i1;
              if (i >= height)
                break;
              var index = i * width + j;

              // significant but not those that have just become
              if (!coefficentsMagnitude[index] ||
                (processingFlags[index] & processedMask) !== 0)
                continue;

              var contextLabel = 16;
              if ((processingFlags[index] &
                firstMagnitudeBitMask) !== 0) {
                processingFlags[i * width + j] ^= firstMagnitudeBitMask;
                // first refinement
                var significance = neighborsSignificance[index];
                var sumOfSignificance = (significance & 3) +
                  ((significance >> 2) & 3) + ((significance >> 4) & 7);
                contextLabel = sumOfSignificance >= 1 ? 15 : 14;
              }

              var cx = contexts[contextLabel];
              var bit = decoder.readBit(cx);
              coefficentsMagnitude[index] =
                (coefficentsMagnitude[index] << 1) | bit;
              bitsDecoded[index]++;
              processingFlags[index] |= processedMask;
            }
          }
        }
      },
      runCleanupPass: function BitModel_runCleanupPass() {
        var decoder = this.decoder;
        var width = this.width, height = this.height;
        var neighborsSignificance = this.neighborsSignificance;
        var significanceState = this.significanceState;
        var coefficentsMagnitude = this.coefficentsMagnitude;
        var coefficentsSign = this.coefficentsSign;
        var contexts = this.contexts;
        var labels = this.contextLabelTable;
        var bitsDecoded = this.bitsDecoded;
        var processingFlags = this.processingFlags;
        var processedMask = 1;
        var firstMagnitudeBitMask = 2;
        var oneRowDown = width;
        var twoRowsDown = width * 2;
        var threeRowsDown = width * 3;
        for (var i0 = 0; i0 < height; i0 += 4) {
          for (var j = 0; j < width; j++) {
            var index0 = i0 * width + j;
            // using the property: labels[neighborsSignificance[index]] == 0
            // when neighborsSignificance[index] == 0
            var allEmpty = i0 + 3 < height &&
              processingFlags[index0] === 0 &&
              processingFlags[index0 + oneRowDown] === 0 &&
              processingFlags[index0 + twoRowsDown] === 0 &&
              processingFlags[index0 + threeRowsDown] === 0 &&
              neighborsSignificance[index0] === 0 &&
              neighborsSignificance[index0 + oneRowDown] === 0 &&
              neighborsSignificance[index0 + twoRowsDown] === 0 &&
              neighborsSignificance[index0 + threeRowsDown] === 0;
            var i1 = 0, index = index0;
            var cx, i;
            if (allEmpty) {
              cx = this.runLengthContext;
              var hasSignificantCoefficent = decoder.readBit(cx);
              if (!hasSignificantCoefficent) {
                bitsDecoded[index0]++;
                bitsDecoded[index0 + oneRowDown]++;
                bitsDecoded[index0 + twoRowsDown]++;
                bitsDecoded[index0 + threeRowsDown]++;
                continue; // next column
              }
              cx = this.uniformContext;
              i1 = (decoder.readBit(cx) << 1) | decoder.readBit(cx);
              i = i0 + i1;
              index += i1 * width;

              var sign = this.decodeSignBit(i, j);
              coefficentsSign[index] = sign;
              coefficentsMagnitude[index] = 1;
              this.setNeighborsSignificance(i, j);
              processingFlags[index] |= firstMagnitudeBitMask;

              index = index0;
              for (var i2 = i0; i2 <= i; i2++, index += width)
                bitsDecoded[index]++;

              i1++;
            }
            for (; i1 < 4; i1++, index += width) {
              i = i0 + i1;
              if (i >= height)
                break;

              if (coefficentsMagnitude[index] ||
                (processingFlags[index] & processedMask) !== 0)
                continue;

              var contextLabel = labels[neighborsSignificance[index]];
              cx = contexts[contextLabel];
              var decision = decoder.readBit(cx);
              if (decision == 1) {
                var sign = this.decodeSignBit(i, j);
                coefficentsSign[index] = sign;
                coefficentsMagnitude[index] = 1;
                this.setNeighborsSignificance(i, j);
                processingFlags[index] |= firstMagnitudeBitMask;
              }
              bitsDecoded[index]++;
            }
          }
        }
      },
      checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() {
        var decoder = this.decoder;
        var cx = this.uniformContext;
        var symbol = (decoder.readBit(cx) << 3) | (decoder.readBit(cx) << 2) |
                     (decoder.readBit(cx) << 1) | decoder.readBit(cx);
        if (symbol != 0xA)
          throw 'Invalid segmentation symbol';
      }
    };

    return BitModel;
  })();

  // Section F, Discrete wavelet transofrmation
  var Transform = (function TransformClosure() {
    function Transform() {
    }
    Transform.prototype.calculate =
      function transformCalculate(subbands, u0, v0) {
      var ll = subbands[0];
      for (var i = 1, ii = subbands.length, j = 1; i < ii; i += 3, j++) {
        ll = this.iterate(ll, subbands[i], subbands[i + 1],
                          subbands[i + 2], u0, v0);
      }
      return ll;
    };
    Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh,
                                                            u0, v0) {
      var llWidth = ll.width, llHeight = ll.height, llItems = ll.items;
      var hlWidth = hl.width, hlHeight = hl.height, hlItems = hl.items;
      var lhWidth = lh.width, lhHeight = lh.height, lhItems = lh.items;
      var hhWidth = hh.width, hhHeight = hh.height, hhItems = hh.items;

      // Section F.3.3 interleave
      var width = llWidth + hlWidth;
      var height = llHeight + lhHeight;
      var items = new Float32Array(width * height);
      for (var i = 0, ii = llHeight; i < ii; i++) {
        var k = i * llWidth, l = i * 2 * width;
        for (var j = 0, jj = llWidth; j < jj; j++, k++, l += 2)
          items[l] = llItems[k];
      }
      for (var i = 0, ii = hlHeight; i < ii; i++) {
        var k = i * hlWidth, l = i * 2 * width + 1;
        for (var j = 0, jj = hlWidth; j < jj; j++, k++, l += 2)
          items[l] = hlItems[k];
      }
      for (var i = 0, ii = lhHeight; i < ii; i++) {
        var k = i * lhWidth, l = (i * 2 + 1) * width;
        for (var j = 0, jj = lhWidth; j < jj; j++, k++, l += 2)
          items[l] = lhItems[k];
      }
      for (var i = 0, ii = hhHeight; i < ii; i++) {
        var k = i * hhWidth, l = (i * 2 + 1) * width + 1;
        for (var j = 0, jj = hhWidth; j < jj; j++, k++, l += 2)
          items[l] = hhItems[k];
      }

      var bufferPadding = 4;
      var bufferLength = new Float32Array(Math.max(width, height) +
        2 * bufferPadding);
      var buffer = new Float32Array(bufferLength);
      var bufferOut = new Float32Array(bufferLength);

      // Section F.3.4 HOR_SR
      for (var v = 0; v < height; v++) {
        if (width == 1) {
          // if width = 1, when u0 even keep items as is, when odd divide by 2
          if ((u0 % 1) !== 0) {
            items[v * width] /= 2;
          }
          continue;
        }

        var k = v * width;
        var l = bufferPadding;
        for (var u = 0; u < width; u++, k++, l++)
          buffer[l] = items[k];

        // Section F.3.7 extending... using max extension of 4
        var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
        var i2 = bufferPadding + width - 2, j2 = bufferPadding + width;
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];

        this.filter(buffer, bufferPadding, width, u0, bufferOut);

        k = v * width;
        l = bufferPadding;
        for (var u = 0; u < width; u++, k++, l++)
          items[k] = bufferOut[l];
      }

      // Section F.3.5 VER_SR
      for (var u = 0; u < width; u++) {
        if (height == 1) {
          // if height = 1, when v0 even keep items as is, when odd divide by 2
          if ((v0 % 1) !== 0) {
            items[u] /= 2;
          }
          continue;
        }

        var k = u;
        var l = bufferPadding;
        for (var v = 0; v < height; v++, k += width, l++)
          buffer[l] = items[k];

        // Section F.3.7 extending... using max extension of 4
        var i1 = bufferPadding - 1, j1 = bufferPadding + 1;
        var i2 = bufferPadding + height - 2, j2 = bufferPadding + height;
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];
        buffer[i1--] = buffer[j1++];
        buffer[j2++] = buffer[i2--];

        this.filter(buffer, bufferPadding, height, v0, bufferOut);

        k = u;
        l = bufferPadding;
        for (var v = 0; v < height; v++, k += width, l++)
          items[k] = bufferOut[l];
      }
      return {
        width: width,
        height: height,
        items: items
      };
    };
    return Transform;
  })();

  // Section 3.8.2 Irreversible 9-7 filter
  var IrreversibleTransform = (function IrreversibleTransformClosure() {
    function IrreversibleTransform() {
      Transform.call(this);
    }

    IrreversibleTransform.prototype = Object.create(Transform.prototype);
    IrreversibleTransform.prototype.filter =
      function irreversibleTransformFilter(y, offset, length, i0, x) {
      var i0_ = Math.floor(i0 / 2);
      var i1_ = Math.floor((i0 + length) / 2);
      var offset_ = offset - (i0 % 1);

      var alpha = -1.586134342059924;
      var beta = -0.052980118572961;
      var gamma = 0.882911075530934;
      var delta = 0.443506852043971;
      var K = 1.230174104914001;
      var K_ = 1 / K;

      // step 1
      var j = offset_ - 2;
      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
        x[j] = K * y[j];

      // step 2
      var j = offset_ - 3;
      for (var n = i0_ - 2, nn = i1_ + 2; n < nn; n++, j += 2)
        x[j] = K_ * y[j];

      // step 3
      var j = offset_ - 2;
      for (var n = i0_ - 1, nn = i1_ + 2; n < nn; n++, j += 2)
        x[j] -= delta * (x[j - 1] + x[j + 1]);

      // step 4
      var j = offset_ - 1;
      for (var n = i0_ - 1, nn = i1_ + 1; n < nn; n++, j += 2)
        x[j] -= gamma * (x[j - 1] + x[j + 1]);

      // step 5
      var j = offset_;
      for (var n = i0_, nn = i1_ + 1; n < nn; n++, j += 2)
        x[j] -= beta * (x[j - 1] + x[j + 1]);

      // step 6
      var j = offset_ + 1;
      for (var n = i0_, nn = i1_; n < nn; n++, j += 2)
        x[j] -= alpha * (x[j - 1] + x[j + 1]);
    };

    return IrreversibleTransform;
  })();

  // Section 3.8.1 Reversible 5-3 filter
  var ReversibleTransform = (function ReversibleTransformClosure() {
    function ReversibleTransform() {
      Transform.call(this);
    }

    ReversibleTransform.prototype = Object.create(Transform.prototype);
    ReversibleTransform.prototype.filter =
      function reversibleTransformFilter(y, offset, length, i0, x) {
      var i0_ = Math.floor(i0 / 2);
      var i1_ = Math.floor((i0 + length) / 2);
      var offset_ = offset - (i0 % 1);

      for (var n = i0_, nn = i1_ + 1, j = offset_; n < nn; n++, j += 2)
        x[j] = y[j] - Math.floor((y[j - 1] + y[j + 1] + 2) / 4);

      for (var n = i0_, nn = i1_, j = offset_ + 1; n < nn; n++, j += 2)
        x[j] = y[j] + Math.floor((x[j - 1] + x[j + 1]) / 2);
    };

    return ReversibleTransform;
  })();

  return JpxImage;
})();

