gt:         final int lastRowOffset = data.length - width;
context: /*
   * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the Free
   * Software Foundation; either version 3 of the License, or (at your option)
   * any later version.
   * This program is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
   * more details.
   *
   * You should have received a copy of the GNU General Public License along
   * with this program; if not, see http://www.gnu.org/licenses/
   */
  
  package org.esa.beam.dataio.chris;
  
  import org.esa.beam.dataio.netcdf.util.NetcdfFileOpener;
  import org.esa.beam.util.io.CsvReader;
  import ucar.ma2.Array;
  import ucar.ma2.DataType;
  import ucar.ma2.InvalidRangeException;
  import ucar.ma2.Section;
  import ucar.nc2.Attribute;
  import ucar.nc2.NetcdfFile;
  import ucar.nc2.Structure;
  import ucar.nc2.Variable;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.InputStreamReader;
  import java.text.MessageFormat;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.TreeMap;
  
  class ChrisFile {
  
      private File file;
      private NetcdfFile ncFile = null;
      private Variable rciImageSds;
      private Variable maskSds;
      private Map<String, String> globalAttributes;
      private Map<Integer, Float> gainInfoMap;
      private float[][] modeInfo;
      private ScanLineLayout scanLineLayout;
      private int sceneRasterHeight;
      private boolean flipped;
  
      private Map<String, ScanLineLayout> scanLineLayoutMap;
  
      public ChrisFile(File file) {
          this.file = file;
  
          try {
              scanLineLayoutMap = readScanLineLayoutMap();
          } catch (IOException e) {
              // ignore
          }
      }
  
      public void open() throws IOException {
          if (ncFile != null) {
              throw new IllegalStateException("already open");
          }
  
          try {
              ncFile = NetcdfFileOpener.open(file.getAbsolutePath());
              globalAttributes = readGlobalAttributes(ncFile);
              rciImageSds = getRciSds(ncFile);
              maskSds = getMaskSds(ncFile);
              modeInfo = readModeInfo(ncFile);
              gainInfoMap = readGainInfo(ncFile);
          } catch (Exception e) {
              try {
                  close();
              } catch (IOException ignored) {
                  // ignore
              }
              final IOException ioe = new IOException(MessageFormat.format("Failed to open CHRIS file ''{0}'':\n{1}", file, e.getMessage()));
              ioe.initCause(e);
  
              throw ioe;
          }
  
          determineProcessingVersion();
          determineSceneRasterHeight();
          determineFlipping();
          determineScanLineLayout();
      }
  
      public void close() throws IOException {
          if (ncFile == null) {
              return;
          }
          try {
              if (globalAttributes != null) {
                  globalAttributes.clear();
              }
              ncFile.close();
          } catch (IOException e) {
              final IOException ioe = new IOException(MessageFormat.format("Failed to close CHRIS file ''{0}''", file));
              ioe.initCause(e);
              throw ioe;
          } finally {
              rciImageSds = null;
              maskSds = null;
              modeInfo = null;
              ncFile = null;
          }
      }
  
      public File getFile() {
          return file;
      }
  
      public int getSceneRasterWidth() {
          return scanLineLayout.imagePixelCount;
      }
  
      public int getSceneRasterHeight() {
          return sceneRasterHeight;
      }
  
      public int getSpectralBandCount() {
          return rciImageSds.getDimension(0).getLength();
      }
  
      public float getCutOnWavelength(int bandIndex) {
          return modeInfo[bandIndex][0];
      }
  
      public float getCutOffWavelength(int bandIndex) {
          return modeInfo[bandIndex][1];
      }
  
      public float getWavelength(int bandIndex) {
          return modeInfo[bandIndex][2];
      }
  
      public float getBandwidth(int bandIndex) {
          return modeInfo[bandIndex][3];
      }
  
      public int getGainSetting(int bandIndex) {
          return (int) modeInfo[bandIndex][4];
      }
  
      public float getGainValue(int bandIndex) {
          final int gainSetting = getGainSetting(bandIndex);
  
          if (gainInfoMap.containsKey(gainSetting)) {
              return gainInfoMap.get(gainSetting);
          }
  
          return Float.NaN;
      }
  
      public int getLowRow(int bandIndex) {
          return (int) modeInfo[bandIndex][5];
      }
  
      public int getHighRow(int bandIndex) {
          return (int) modeInfo[bandIndex][6];
      }
  
      public boolean hasMask() {
          return maskSds != null;
      }
  
      public void readRciData(int bandIndex,
                              int offsetX,
                              int offsetY,
                              int stepX,
                              int stepY,
                              int width,
                              int height,
                              int[] data) throws IOException {
          try {
              offsetX += scanLineLayout.leadingPixelCount;
              if (flipped) {
                  offsetY = sceneRasterHeight - offsetY - height;
              }
  
              final int[] start = new int[]{bandIndex, offsetY, offsetX};
              final int[] stride = new int[]{1, stepY, stepX};
              final int[] count = new int[]{1, height, width};
  
              Section section = new Section(start, count, stride);
              Array array = rciImageSds.read(section);
              final Object storage = array.getStorage();
              System.arraycopy(storage, 0, data, 0, data.length);
              if (flipped) {
                  flipImage(data, width);
              }
          } catch (InvalidRangeException e) {
              final IOException ioe = new IOException(
                      MessageFormat.format("Failed to read data from band #{0} of ''{1}''", bandIndex + 1,
                                           rciImageSds.getFullName()));
              ioe.initCause(e);
              throw ioe;
          }
      }
  
      public void readMaskData(int bandIndex,
                               int offsetX,
                               int offsetY,
                               int stepX,
                               int stepY,
                               int width,
                               int height,
                               short[] mask) throws IOException {
  
          try {
              offsetX += scanLineLayout.leadingPixelCount;
              if (flipped) {
                  offsetY = sceneRasterHeight - offsetY - height;
              }
  
              final int[] start = new int[]{bandIndex, offsetY, offsetX};
              final int[] stride = new int[]{1, stepY, stepX};
              final int[] count = new int[]{1, height, width};
  
              Section section = new Section(start, count, stride);
              Array array = maskSds.read(section);
              final Object storage = array.getStorage();
              System.arraycopy(storage, 0, mask, 0, mask.length);
              if (flipped) {
                  flipImage(mask, width);
              }
          } catch (InvalidRangeException e) {
              final IOException ioe = new IOException(
                      "Failed to read data from band #" + (bandIndex + 1) + " of '" + maskSds.getFullName() + "'");
              ioe.initCause(e);
              throw ioe;
          }
      }
  
      private void flipImage(int[] data, int width) {
          final int[] temp = new int[width];
          final int lastRowOffset = data.length - width;
  
          for (int i = 0; i < data.length / 2; i += width) {
              System.arraycopy(data, i, temp, 0, width);
              System.arraycopy(data, lastRowOffset - i, data, i, width);
              System.arraycopy(temp, 0, data, lastRowOffset - i, width);
          }
      }
  
      private void flipImage(short[] data, int width) {
          final short[] temp = new short[width];
  