/**
   * Quick method for populating the {@link CUDABean} instance provided.
   *
   * @param bean
   * @param reference
   * @param coverage
   * @param geo
   * @param transform
   * @throws IOException
   * @throws MismatchedDimensionException
   * @throws TransformException
   */
  private void populateBean(
      CUDABean bean,
      boolean reference,
      GridCoverage2D coverage,
      Geometry geo,
      MathTransform transform,
      int buffer)
      throws IOException, MismatchedDimensionException, TransformException {

    RenderedImage image = coverage.getRenderedImage();

    // 0) Check if a buffer must be applied
    Geometry originalGeo = (Geometry) geo.clone();
    if (buffer > 0) {
      try {
        if (!"EPSG:4326"
            .equals(CRS.lookupIdentifier(coverage.getCoordinateReferenceSystem(), false))) {
          geo = geo.buffer(buffer);
        } else {
          geo = geo.buffer(buffer / 111.128);
        }
      } catch (FactoryException e) {
        geo = geo.buffer(buffer);
      }
    }

    // 1) Crop the two coverages with the selected Geometry
    GridCoverage2D crop = CROP.execute(coverage, geo, null);
    transform =
        ProjectiveTransform.create(
                (AffineTransform) crop.getGridGeometry().getGridToCRS(PixelInCell.CELL_CORNER))
            .inverse();

    // 2) Extract the BufferedImage from each image
    image = crop.getRenderedImage();

    Rectangle rectIMG =
        new Rectangle(image.getMinX(), image.getMinY(), image.getWidth(), image.getHeight());
    ImageWorker w = new ImageWorker(image);
    BufferedImage buf = w.getBufferedImage();
    if (image instanceof RenderedOp) {
      ((RenderedOp) image).dispose();
    }

    // 3) Generate an array of data from each image
    Raster data = buf.getData();
    final DataBufferByte db = (DataBufferByte) data.getDataBuffer();
    byte[] byteData = db.getData();

    if (reference) {
      // 4) Transform the Geometry to Raster space
      Geometry rs = JTS.transform(geo, transform);
      Geometry rsFilter = JTS.transform(geo.difference(originalGeo), transform);
      ROI roiGeo = new ROIGeometry(rs);
      ROI roiFilter = new ROIGeometry(rsFilter);

      // 5) Extract an array of data from the transformed ROI
      byte[] roiData = getROIData((buffer > 0 ? roiFilter : roiGeo), rectIMG);
      bean.setRoi(roiData);
      bean.setRoiObj(roiGeo);

      // 6) Setting the Coverage data array
      bean.setReferenceImage(byteData);

      // 7) Setting the Image dimensions
      bean.setHeight(rectIMG.height);
      bean.setWidth(rectIMG.width);
      bean.setMinX(rectIMG.x);
      bean.setMinY(rectIMG.y);
    } else {
      // 6) Setting the Coverage data array
      bean.setCurrentImage(byteData);
    }

    // 7) Store the Reference Covergae containing the geospatial info
    bean.setReferenceCoverage(coverage);
  }
  @DescribeResult(
      name = "UrbanGridCUDAProcess",
      description = "Urban Grid indexes calculated using CUDA",
      type = List.class)
  public List<StatisticContainer> execute(
      @DescribeParameter(name = "reference", description = "Name of the reference raster")
          GridCoverage2D referenceCoverage,
      @DescribeParameter(name = "now", description = "Name of the new raster")
          GridCoverage2D nowCoverage,
      @DescribeParameter(name = "index", min = 1, description = "Index to calculate") int index,
      @DescribeParameter(
              name = "subindex",
              min = 0,
              description = "String indicating which sub-index must be calculated")
          String subId,
      @DescribeParameter(name = "pixelarea", min = 0, description = "Pixel Area") Double pixelArea,
      @DescribeParameter(name = "rois", min = 1, description = "Administrative Areas")
          List<Geometry> rois,
      @DescribeParameter(name = "populations", min = 0, description = "Populations for each Area")
          List<List<Integer>> populations,
      @DescribeParameter(
              name = "coefficient",
              min = 0,
              description = "Multiplier coefficient for index 10")
          Double coeff,
      @DescribeParameter(name = "rural", min = 0, description = "Rural or Urban index")
          boolean rural,
      @DescribeParameter(name = "radius", min = 0, description = "Radius in meters") int radius)
      throws IOException {

    // Check on the index 7
    boolean nullSubId = subId == null || subId.isEmpty();
    boolean subIndexA = !nullSubId && subId.equalsIgnoreCase("a");
    boolean subIndexC = !nullSubId && subId.equalsIgnoreCase("c");
    boolean subIndexB = !nullSubId && subId.equalsIgnoreCase("b");
    if (index == SEVENTH_INDEX && (nullSubId || !(subIndexA || subIndexB || subIndexC))) {
      throw new IllegalArgumentException("Wrong subindex for index 7");
    }
    // Check if almost one coverage is present
    if (referenceCoverage == null && nowCoverage == null) {
      throw new IllegalArgumentException("No input Coverage provided");
    }

    double areaPx;
    if (pixelArea == null) {
      areaPx = PIXEL_AREA;
    } else {
      areaPx = pixelArea;
    }

    // Check if Geometry area or perimeter must be calculated
    boolean inRasterSpace = true;
    // Selection of the operation to do for each index
    switch (index) {
      case FIFTH_INDEX:
      case SIXTH_INDEX:
      case SEVENTH_INDEX:
      case ELEVENTH_INDEX:
      case TWELVE_INDEX:
        if (!subIndexA) {
          inRasterSpace = false;
        }
        break;
      default:
        break;
    }

    // If the index is 7a-8-9-10 then the input Geometries must be transformed to the Model Space
    List<Geometry> geoms = new ArrayList<Geometry>();
    final AffineTransform gridToWorldCorner =
        (AffineTransform)
            ((GridGeometry2D) referenceCoverage.getGridGeometry())
                .getGridToCRS2D(PixelOrientation.UPPER_LEFT);
    if (inRasterSpace) {
      for (Geometry geo : rois) {
        try {
          geoms.add(JTS.transform(geo, ProjectiveTransform.create(gridToWorldCorner)));
        } catch (MismatchedDimensionException e) {
          LOGGER.log(Level.SEVERE, e.getMessage(), e);
          throw new ProcessException(e);
        } catch (TransformException e) {
          LOGGER.log(Level.SEVERE, e.getMessage(), e);
          throw new ProcessException(e);
        }
      }
    } else {
      geoms.addAll(rois);
    }

    // Check if the Geometries must be reprojected
    /*        Object userData = geoms.get(0).getUserData();
            if (!inRasterSpace && userData instanceof CoordinateReferenceSystem) {
                CoordinateReferenceSystem geomCRS = (CoordinateReferenceSystem) userData;
                CoordinateReferenceSystem refCRS = referenceCoverage.getCoordinateReferenceSystem();
                MathTransform tr = null;
                try {
                    tr = CRS.findMathTransform(geomCRS, refCRS);

                    if (!(tr == null || tr.isIdentity())) {
                        int geosize = geoms.size();
                        for (int i = 0; i < geosize; i++) {
                            Geometry geo = geoms.get(i);
                            Geometry transform = JTS.transform(geo, tr);
                            transform.setUserData(refCRS);
                            geoms.set(i, transform);
                        }
                    }
                } catch (Exception e) {
                    LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    throw new ProcessException(e);
                }
                // Otherwise only set the correct User_Data parameter
            } else if (inRasterSpace){
    */ int geosize = geoms.size();
    final CoordinateReferenceSystem refCrs = referenceCoverage.getCoordinateReferenceSystem();
    for (int i = 0; i < geosize; i++) {
      Geometry geo = geoms.get(i);

      geo.setUserData(refCrs);

      if (geo.getSRID() == 0) {
        try {
          geo.setSRID(CRS.lookupEpsgCode(refCrs, true));
        } catch (FactoryException e) {
          LOGGER.log(Level.WARNING, e.getMessage(), e);
        }
      }
    }
    //        }

    // Empty arrays containing the statistics results
    double[] statsRef = null;
    double[] statsNow = null;
    double[][][] statsComplex = null;

    // Create a new List of CUDA Bean objects
    List<CUDABean> beans = new ArrayList<CUDABean>();

    // Loop around all the Geometries and generate a new CUDA Bean object
    try {
      // MathTransform transform = ProjectiveTransform.create(gridToWorldCorner).inverse();
      int counter = 0;
      int buffer = (index == 12 ? radius : 0);
      for (Geometry geo : geoms) {
        // Create the CUDABean object
        CUDABean bean = new CUDABean();
        bean.setAreaPix(areaPx);

        // Populate it with Reference coverage parameters
        populateBean(bean, true, referenceCoverage, geo, null, buffer);

        // Set the population values if needed
        if (populations != null) {
          Integer popRef = populations.get(0).get(counter);
          bean.setPopRef(popRef);
        }

        // Do the same for the Current Coverage if present
        if (nowCoverage != null) {
          populateBean(bean, false, nowCoverage, geo, null, buffer);
          // Set the population values if needed
          if (populations != null) {
            Integer popCur = populations.get(1).get(counter);
            bean.setPopCur(popCur);
          }
        }
        // Add the bean to the list
        beans.add(bean);
        // Update counter
        counter++;
      }
    } catch (Exception e) {
      LOGGER.log(Level.SEVERE, e.getMessage(), e);
      throw new ProcessException(e);
    }

    // Calculate the index using CUDA
    //		System.out.println(
    // java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime()) );
    //      long startTime = System.currentTimeMillis();
    /**
     * Generalize: > isUrban = false/true ------------------------| > RADIUS [meters] = scalar
     * ---------------------------------|
     */
    Object output = null;
    try {
      output = calculateCUDAIndex(index, subId, beans, rural, radius);
    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    //		long estimatedTime = System.currentTimeMillis() - startTime;
    //		System.out.println("Elapsed time calculateCUDAIndex()\t--> " + estimatedTime + " [ms]");
    Rectangle refRect =
        PlanarImage.wrapRenderedImage(referenceCoverage.getRenderedImage()).getBounds();

    // For index 8 calculate the final Image
    if (index == 8 || index == 9 || index == 12) {

      System.out.println("rural=" + rural + " -- radius/buffer=" + radius + " [m]");

      List<StatisticContainer> results = new ArrayList<CLCProcess.StatisticContainer>();
      StatisticContainer stats = new StatisticContainer();
      double[][][] images = (double[][][]) output;

      int numGeo = beans.size();
      // Images to mosaic
      RenderedImage[] refImgs = new RenderedImage[numGeo];
      ROI[] roiObjs = new ROI[numGeo];

      // Giuliano tested for 91 municipalities in NAPLES and it FAILED within the following FOR
      // loop!!
      for (int i = 0; i < numGeo; i++) {
        double[][] refData = images[i];
        CUDABean bean = beans.get(i);
        double[] data = refData[0];
        if (data != null) {
          Rectangle rect =
              new Rectangle(bean.getMinX(), bean.getMinY(), bean.getWidth(), bean.getHeight());
          refImgs[i] = createImage(rect, data);
          roiObjs[i] = bean.getRoiObj();
        }
      }
      ImageLayout layout = new ImageLayout2();
      layout.setMinX(refRect.x);
      layout.setMinY(refRect.y);
      layout.setWidth(refRect.width);
      layout.setHeight(refRect.height);

      RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);

      // Mosaic of the images
      double[] background = (index == 8 || index == 12 ? new double[] {-1.0} : new double[] {0.0});
      RenderedImage finalRef =
          MosaicDescriptor.create(
              refImgs,
              MosaicDescriptor.MOSAIC_TYPE_OVERLAY,
              null,
              roiObjs,
              null,
              background,
              hints);

      // RenderedImageBrowser.showChain(finalRef, false, false);

      // Upgrade of the statistics container
      stats.setReferenceImage(finalRef);
      // Check if the same calculations must be done for the Current coverage
      if (nowCoverage != null && index != 9) {
        RenderedImage[] currImgs = new RenderedImage[numGeo];
        RenderedImage[] diffImgs = new RenderedImage[numGeo];
        for (int i = 0; i < numGeo; i++) {
          CUDABean bean = beans.get(i);
          double[] data = images[i][1];
          double[] diff = images[i][2];
          Rectangle rect =
              new Rectangle(bean.getMinX(), bean.getMinY(), bean.getWidth(), bean.getHeight());
          currImgs[i] = createImage(rect, data);
          diffImgs[i] = createImage(rect, diff);
        }
        // Mosaic of the images
        RenderedImage finalCurr =
            MosaicDescriptor.create(
                currImgs,
                MosaicDescriptor.MOSAIC_TYPE_OVERLAY,
                null,
                roiObjs,
                null,
                background,
                hints);

        // Mosaic of the images
        RenderedImage finalDiff =
            MosaicDescriptor.create(
                diffImgs,
                MosaicDescriptor.MOSAIC_TYPE_OVERLAY,
                null,
                roiObjs,
                null,
                background,
                hints);
        // Update the statistics container
        stats.setNowImage(finalCurr);
        stats.setDiffImage(finalDiff);
      }
      results.add(stats);
      return results;
    }
    /*else if (index == 9) {// LAND TAKE
        double[][][] values = (double[][][]) output;
        statsRef = values[0][0];
        statsNow = values[0].length > 1 ? values[0][1] : null;
    }*/
    else if (index == 11) { // MODEL OF URBAN DEVELOPMENT
      statsComplex = (double[][][]) output;
    } else {
      double[][][] values = (double[][][]) output;
      statsRef = new double[values.length];
      statsNow = (values[0][0].length > 1 ? new double[values.length] : null);

      for (int v = 0; v < values.length; v++) {
        statsRef[v] = values[v][0][0];
        if (values[v][0].length > 1) {
          statsNow[v] = values[v][0][1];
        }
      }
    }

    // Result accumulation
    List<StatisticContainer> results = accumulateResults(rois, statsRef, statsNow, statsComplex);

    return results;
  }