/** This method creates the target product */
  private void createTargetProduct() {

    final String productType = synergyProduct.getProductType();
    final String productName = synergyProduct.getName();
    final int sceneWidth = synergyProduct.getSceneRasterWidth();
    final int sceneHeight = synergyProduct.getSceneRasterHeight();

    final int downscaledRasterWidth = (int) (Math.ceil((float) (sceneWidth / scalingFactor) - 0.5));
    final int downscaledRasterHeight =
        (int) (Math.ceil((float) (sceneHeight / scalingFactor) - 0.5));

    targetProduct =
        new Product(productName, productType, downscaledRasterWidth, downscaledRasterHeight);
    //        targetProduct.setPreferredTileSize(128, 128);

    ProductUtils.copyGeoCoding(synergyProduct, targetProduct);
    ProductUtils.copyMetadata(synergyProduct, targetProduct);
    AerosolHelpers.copyDownscaledTiePointGrids(synergyProduct, targetProduct, scalingFactor);
    AerosolHelpers.copyDownscaledFlagBands(synergyProduct, targetProduct, scalingFactor);

    //        AerosolHelpers.addAerosolFlagBand(targetProduct, downscaledRasterWidth,
    // downscaledRasterHeight);

    final BandMathsOp bandArithmeticOp =
        BandMathsOp.createBooleanExpressionBand(INVALID_EXPRESSION, synergyProduct);
    invalidBand = bandArithmeticOp.getTargetProduct().getBandAt(0);

    setTargetBands();
  }
  /**
   * This method finds for each regular angstroem parameter a PAIR of models which can be used for a
   * weighted sum (interpolation). The method represents the breadboard IDL routine
   * 'find_model_pairs_for_angstroem_interpolation'.
   *
   * @param angArray - array of Angstroem coefficients from aerosol model table
   * @param nAng - number of Ang coeffs
   * @return AngstroemParameters[] - the model pairs
   */
  public AngstroemParameters[] getAngstroemParameters(float[] angArray, int nAng) {
    final float[] minMaxVector = AerosolHelpers.getMinMaxVector(angArray, nAng);
    AngstroemParameters[] angstroemParameters = new AngstroemParameters[minMaxVector.length];

    for (int i = 0; i < minMaxVector.length; i++) {
      int lowerIndex =
          AerosolHelpers.getNearestLowerValueIndexInFloatArray(minMaxVector[i], angArray);
      angstroemParameters[i] = new AngstroemParameters();
      angstroemParameters[i].setIndexPairs(0, lowerIndex);
      int higherIndex =
          AerosolHelpers.getNearestHigherValueIndexInFloatArray(minMaxVector[i], angArray);
      angstroemParameters[i].setIndexPairs(1, higherIndex);
      angstroemParameters[i].setValue(minMaxVector[i]);
    }

    for (int i = 0; i < minMaxVector.length; i++) {
      float lowerAng = angArray[angstroemParameters[i].getIndexPairs()[0]];
      float higherAng = angArray[angstroemParameters[i].getIndexPairs()[1]];

      float distance = lowerAng - higherAng;
      if (Math.abs(distance) > 0.01) {
        double wgt0 = 1.0 - (lowerAng - minMaxVector[i]) / distance;
        angstroemParameters[i].setWeightPairs(0, wgt0);
        double wgt1 = 1.0 - (minMaxVector[i] - higherAng) / distance;
        angstroemParameters[i].setWeightPairs(1, wgt1);
      } else {
        angstroemParameters[i].setWeightPairs(0, 1.0);
        angstroemParameters[i].setWeightPairs(1, 0.0);
      }
    }

    return angstroemParameters;
  }
  /**
   * This method copies all bands which contain a flagcoding from the source product to the target
   * product.
   *
   * @param sourceProduct the source product
   * @param targetProduct the target product
   */
  public static void copyDownscaledFlagBands(
      Product sourceProduct, Product targetProduct, float scalingFactor) {
    Guardian.assertNotNull("source", sourceProduct);
    Guardian.assertNotNull("target", targetProduct);
    if (sourceProduct.getFlagCodingGroup().getNodeCount() > 0) {

      ProductUtils.copyFlagCodings(sourceProduct, targetProduct);
      ProductUtils.copyMasks(sourceProduct, targetProduct);

      // loop over bands and check if they have a flags coding attached
      for (int i = 0; i < sourceProduct.getNumBands(); i++) {
        Band sourceBand = sourceProduct.getBandAt(i);
        FlagCoding coding = sourceBand.getFlagCoding();
        if (coding != null) {
          Band targetBand = AerosolHelpers.downscaleBand(sourceBand, scalingFactor);
          targetBand.setSampleCoding(coding);
          targetProduct.addBand(targetBand);
        }
      }
    }
  }
  private float[] doSynAOStep1(
      float aatsrViewElevationNadir,
      float aatsrViewElevationFward,
      float aatsrSunElevationNadir,
      float aatsrSunElevationFward,
      float aatsrAzimuthDifferenceNadir,
      float aatsrAzimuthDifferenceFward,
      float merisViewZenith,
      float merisSunZenith,
      float merisAzimuthDifference,
      float surfacePressure,
      float ws) {

    float[] glint = new float[nWvl];
    float[] iSun = new float[nWvl];
    float[] iView = new float[nWvl];
    float[] iAzi = new float[nWvl];
    for (int j = 0; j < nWvl; j++) {
      // todo: clean up cases for finally unused channels
      switch (wvlIndex[j]) {
        case 0:
        case 1:
          iSun[j] = merisSunZenith;
          iView[j] = merisViewZenith;
          iAzi[j] = (float) (180.0 - merisAzimuthDifference);
          break;
        case 2:
        case 3:
          iSun[j] = (float) (90.0 - aatsrSunElevationNadir);
          iView[j] = (float) (90.0 - aatsrViewElevationNadir);
          iAzi[j] = (float) (180.0 - aatsrAzimuthDifferenceNadir);
          break;
        case 4:
        case 5:
          iSun[j] = (float) (90.0 - aatsrSunElevationFward);
          iView[j] = (float) (90.0 - aatsrViewElevationFward);
          iAzi[j] = (float) (180.0 - aatsrAzimuthDifferenceFward);
          break;
        default:
          break;
      }
      glint[j] =
          GlintRetrieval.calcGlintAnalytical(
              iSun[j],
              iView[j],
              iAzi[j],
              SynergyConstants.refractiveIndex[wvlIndex[j]],
              ws,
              SynergyConstants.rhoFoam[wvlIndex[j]]);
    }

    vectorTauLutHigh = AerosolHelpers.interpolateArray(vectorTauLut, nTau);

    for (int i = 0; i < nMod; i++) {
      for (int j = 0; j < nWvl; j++) {
        // todo: clean up cases for finally unused channels

        for (int k = 0; k < nTauLut; k++) {
          double[] interpol5DLowInput =
              new double[] {iAzi[j], iView[j], iSun[j], ws, vectorTauLut[k], surfacePressure};

          // interpol5DResultLow = 'minilut' in breadboard:
          //  minilut=fltarr(nmod,nwvl,ntau)
          interpol5DResultLow[i][j][k] = // 'minilut' in breadboard
              aerosolLookupTables[i][j].getValue(interpol5DLowInput);
        }
        //  interpol5DResultLow --> interpol5DResultHigh
        interpol5DResultHigh[i][j] =
            AerosolHelpers.interpolateArray(interpol5DResultLow[i][j], nTau);
        for (int k = 0; k < nTau; k++) {
          interpol5DResultHigh[i][j][k] += glint[j];
        }
      }
    }
    return glint;
  }
  public void initialize() throws OperatorException {
    //        System.out.println("starting...");

    if (new File(SynergyConstants.SYNERGY_AUXDATA_HOME_DEFAULT).exists()) {
      auxdataPath =
          SynergyConstants.SYNERGY_AUXDATA_HOME_DEFAULT
              + File.separator
              + "aerosolLUTs"
              + File.separator
              + "ocean";
    } else {
      // try this one (in case of calvalus processing)
      auxdataPath = SynergyConstants.SYNERGY_AUXDATA_CALVALUS_DEFAULT;
    }

    noDataVal = (float) SynergyConstants.OUTPUT_AOT_BAND_NODATAVALUE;

    // get the glint product...
    Map<String, Product> glintInput = new HashMap<String, Product>(3);
    glintInput.put("l1bSynergy", synergyProduct);
    Map<String, Object> glintAveParams = new HashMap<String, Object>(2);
    glintAveParams.put("aveBlock", aveBlock);
    glintProduct =
        GPF.createProduct(
            OperatorSpi.getOperatorAlias(GlintAveOp.class), glintAveParams, glintInput);

    scalingFactor = aveBlock;
    aveBlock /= 2;
    minNAve = (int) (scalingFactor * scalingFactor - 1);
    noDataVal = (float) SynergyConstants.OUTPUT_AOT_BAND_NODATAVALUE;

    createTargetProduct();
    //        targetProduct = glintProduct;       // test

    // correction of azimuth discontinuity:
    // set up tiles for MERIS and AATSR which cover the whole scene...
    final int sceneWidth = synergyProduct.getSceneRasterWidth();
    final int sceneHeight = synergyProduct.getSceneRasterHeight();
    final Rectangle rect = new Rectangle(0, 0, sceneWidth, sceneHeight);
    vaMerisTileComplete = getSourceTile(synergyProduct.getTiePointGrid("view_azimuth"), rect);
    vaAatsrNadirTileComplete =
        getSourceTile(
            synergyProduct.getBand(
                "view_azimuth_nadir" + "_" + SynergyConstants.INPUT_BANDS_SUFFIX_AATSR + ""),
            rect);

    aot550Result = new float[sceneWidth][sceneHeight];
    angResult = new float[sceneWidth][sceneHeight];
    aot550ErrorResult = new float[sceneWidth][sceneHeight];
    angErrorResult = new float[sceneWidth][sceneHeight];
    glintResult = new float[sceneWidth][sceneHeight];
    wsResult = new float[sceneWidth][sceneHeight];

    // read aerosol class table
    try {
      aerosolClassTable = AerosolAuxData.getInstance().createAerosolClassTable();
    } catch (IOException e) {
      throw new OperatorException("Failed to read aerosol class table:\n" + e.getMessage(), e);
    }

    // read aerosol models
    try {
      aerosolModelTable = AerosolAuxData.getInstance().createAerosolModelTable(auxdataPath);
    } catch (IOException e) {
      throw new OperatorException("Failed to read aerosol class table:\n" + e.getMessage(), e);
    }

    //        wvl=[ 865,  885,1610,   885,1610,   885]
    wvl = new float[] {865.0f, 885.0f, 1610.0f, 885.0f, 1610.0f, 885.0f};
    wvlWeight = new float[] {1.0f, 1.0f, 3.0f, 1.0f, 3.0f, 3.0f};
    wvlIndex = new int[] {0, 3, 5};
    // at this point, just use 1 MERIS and 1 AATSR channel...
    // todo: clarify with RP which we should finally use

    // find model indices belonging to aerosol classes...
    final List<Integer> modelIndices = aerosolModelTable.getMaritimeAndDesertIndices();
    nMod = modelIndices.size();
    nWvl = wvlIndex.length;

    try {
      aerosolLookupTables =
          AerosolAuxData.getInstance()
              .createAerosolOceanLookupTables(
                  auxdataPath, modelIndices,
                  wvl, wvlIndex);
    } catch (IOException e) {
      throw new OperatorException("Failed to create aerosol lookup tables:\n" + e.getMessage(), e);
      //            String msg = SynergyConstants.AUXDATA_ERROR_MESSAGE;
      //            SynergyUtils.logErrorMessage(msg);
    }
    nTauLut = aerosolLookupTables[0][0].getDimensions()[4].getSequence().length;

    interpol5DResultLow = new double[nMod][nWvl][nTauLut];
    interpol5DResultHigh = new double[nMod][nWvl][nTau];
    interpolAngResult = new float[nWvl][nTau][nAng];
    costFunction = new float[nWvl][nTau][nAng];

    vectorTauLut = new double[nTauLut];
    for (int i = 0; i < nTauLut; i++) {
      vectorTauLut[i] = i * 2.0 / (nTauLut - 1);
    }
    vectorTauLutHigh = new double[nTau];
    for (int i = 0; i < nTau; i++) {
      vectorTauLutHigh[i] = i * 2.0 / (nTau - 1);
    }

    final float[] angArray = aerosolModelTable.getAngArray(modelIndices, 0);
    angstroemParameters = AerosolHelpers.getInstance().getAngstroemParameters(angArray, nAng);

    // read corresponding small LUTs and make a big LUT...

    // correct azimuths in these tiles for later usage...
    GlintPreparation.correctViewAzimuthLinear(vaMerisTileComplete, rect);
    GlintPreparation.correctViewAzimuthLinear(vaAatsrNadirTileComplete, rect);
  }