public GhostReport ghostCalculation(int maxImageSmallDimension, int numThreads)
        throws IOException {
      File ghostOutputfile;
      byte[] imageInByte;
      String ghostBase64; // convert to base64 the image file
      GhostReport ghostReport = new GhostReport();
      GhostExtractor ghostExtractor;
      ghostExtractor = new GhostExtractor(sourceFile, maxImageSmallDimension, numThreads);
      BufferedImage ghostMap;
      for (int ghostMapInd = 0; ghostMapInd < ghostExtractor.ghostMaps.size(); ghostMapInd++) {
        ghostOutputfile =
            new File(baseFolder, "GhostOutput" + String.format("%02d", ghostMapInd) + ".png");
        ghostMap = ghostExtractor.ghostMaps.get(ghostMapInd);
        ImageIO.write(ghostMap, "png", ghostOutputfile);
        ByteArrayOutputStream ghostbytes = new ByteArrayOutputStream();
        ImageIO.write(ghostMap, "png", ghostbytes);
        imageInByte = ghostbytes.toByteArray();

        ghostReport.maps.add(ghostOutputfile.getCanonicalPath());
        ghostReport.differences = ghostExtractor.allDifferences;
        ghostReport.minQuality = ghostExtractor.qualityMin;
        ghostReport.maxQuality = ghostExtractor.qualityMax;
        ghostReport.qualities = ghostExtractor.ghostQualities;
        ghostReport.minValues = ghostExtractor.ghostMin;
        ghostReport.maxValues = ghostExtractor.ghostMax;
      }
      ghostReport.completed = true;
      return ghostReport;
    }
  public static String reportCalculation(
      String urlHash,
      String mongoHostIP,
      String folderOut,
      int maxGhostImageSmallDimension,
      int numGhostThreads,
      int numberOfThreads,
      long computationTimeoutLimit)
      throws UnknownHostException {
    String outMessage = "COMPLETEDSUCCESSFULLY";

    ExecutorService threadpool = Executors.newFixedThreadPool(numberOfThreads);
    MongoClient mongoclient = new MongoClient(mongoHostIP, 27017);
    Morphia morphia = new Morphia();
    morphia.map(ForensicReport.class).map(dqReport.class);
    Datastore ds = new Morphia().createDatastore(mongoclient, "ForensicDatabase");
    ds.ensureCaps();
    String baseFolder = folderOut + urlHash + "/";
    ForensicReport report = ds.get(ForensicReport.class, urlHash);

    if (report == null) {
      mongoclient.close();
      return "HASHNOTFOUND";
    }
    if (report.status.equalsIgnoreCase("Processing")) {
      mongoclient.close();
      return "ALREADYPROCESSING";
    }
    if (report.status.equalsIgnoreCase("Done")) {
      mongoclient.close();
      return "PROCESSINGALREADYCOMPLETE";
    }
    System.out.println("start processing");
    report.status = "Processing";
    dqReport dqReport = new dqReport();
    ELAReport elaReport = new ELAReport();
    GhostReport ghostReport = new GhostReport();
    DWNoiseReport dwNoiseReport = new DWNoiseReport();
    BlockingReport blockingReport = new BlockingReport();
    MedianNoiseReport medianNoiseReport = new MedianNoiseReport();
    GPSReport gpsReport = new GPSReport();
    GridsNormalReport gridsReport = new GridsNormalReport();
    GridsInversedReport gridsInversedReport = new GridsInversedReport();

    File dqOutputfile = new File(baseFolder, "DQOutput.png");
    File dwNoiseOutputfile = new File(baseFolder, "DWNoiseOutput.png");
    File ghostOutputfile;
    File elaOutputfile = new File(baseFolder, "ELAOutput.png");
    File blkOutputfile = new File(baseFolder, "BLKOutput.png");
    File medianNoiseOutputFile = new File(baseFolder, "MedianNoiseOutput.png");
    File gridsOutputFile = new File(baseFolder, "GridsOutput.png");
    File gridsInversedOutputFile = new File(baseFolder, "GridsInversedOutput.png");
    try {
      if (ImageIO.read(new File(report.sourceImage)).getColorModel().hasAlpha()) {
        System.out.println(
            "If image has an alpha channel, then assume transparent PNG -No point in processing it");
        // If image has an alpha channel, then assume transparent PNG -No point in processing it
        BufferedImage transparentPNGNotAccepted = ArtificialImages.transparentPNGNotAccepted();
        ImageIO.write(transparentPNGNotAccepted, "png", dqOutputfile);
        System.out.println(
            " dqOutputfile.getCanonicalPath() 1 :: " + dqOutputfile.getCanonicalPath());
        dqReport.map = dqOutputfile.getCanonicalPath();
        dqReport.completed = true;
        report.dqReport = dqReport;
        ImageIO.write(transparentPNGNotAccepted, "png", dwNoiseOutputfile);
        System.out.println(
            " dwNoiseOutputfile.getCanonicalPath() :: " + dwNoiseOutputfile.getCanonicalPath());
        dwNoiseReport.map = dwNoiseOutputfile.getCanonicalPath();
        dwNoiseReport.completed = true;
        report.dwNoiseReport = dwNoiseReport;
        ghostOutputfile = new File(baseFolder, "GhostOutput" + String.format("%02d", 0) + ".png");
        ImageIO.write(transparentPNGNotAccepted, "png", ghostOutputfile);
        ghostReport.maps.add(ghostOutputfile.getCanonicalPath());
        System.out.println(
            " ghostOutputfile.getCanonicalPath() :: " + ghostOutputfile.getCanonicalPath());
        ghostReport.differences.add((float) 0.0);
        ghostReport.qualities.add(0);
        ghostReport.minValues.add((float) 0.0);
        ghostReport.maxValues.add((float) 0.0);
        ghostReport.completed = true;
        report.ghostReport = ghostReport;
        ImageIO.write(transparentPNGNotAccepted, "png", elaOutputfile);
        System.out.println(
            " elaOutputfile.getCanonicalPath() :: " + elaOutputfile.getCanonicalPath());
        elaReport.map = elaOutputfile.getCanonicalPath();
        elaReport.completed = true;
        report.elaReport = elaReport;
        ImageIO.write(transparentPNGNotAccepted, "png", blkOutputfile);
        blockingReport.map = blkOutputfile.getCanonicalPath();
        System.out.println(
            " blkOutputfile.getCanonicalPath() :: " + blkOutputfile.getCanonicalPath());
        blockingReport.completed = true;
        report.blockingReport = blockingReport;
        ImageIO.write(transparentPNGNotAccepted, "png", medianNoiseOutputFile);
        medianNoiseReport.map = medianNoiseOutputFile.getCanonicalPath();
        System.out.println(
            " medianNoiseOutputFile.getCanonicalPath() :: "
                + medianNoiseOutputFile.getCanonicalPath());
        medianNoiseReport.completed = true;
        report.medianNoiseReport = medianNoiseReport;
        ImageIO.write(transparentPNGNotAccepted, "png", gridsOutputFile);
        gridsReport.map = gridsOutputFile.getCanonicalPath();
        System.out.println(
            " gridsOutputFile.getCanonicalPath() :: " + gridsOutputFile.getCanonicalPath());
        ImageIO.write(transparentPNGNotAccepted, "png", gridsInversedOutputFile);
        gridsInversedReport.map = gridsInversedOutputFile.getCanonicalPath();
        System.out.println(
            " gridsInversedOutputFile.getCanonicalPath() :: "
                + gridsInversedOutputFile.getCanonicalPath());
        gridsReport.completed = true;
        gridsInversedReport.completed = true;
        report.gridsReport = gridsReport;
        report.gridsInversedReport = gridsInversedReport;
        ds.save(report);
      } else {

        Boolean dqSaved = false,
            noiseDWSaved = false,
            ghostSaved = false,
            elaSaved = false,
            blkSaved = false,
            medianNoiseSaved = false,
            gridsSaved = false;
        DQThread dqThread = new DQThread(report.sourceImage, dqOutputfile);
        Future dqFuture = threadpool.submit(dqThread);
        noiseDWThread noiseDWThread = new noiseDWThread(report.sourceImage, dwNoiseOutputfile);
        Future noiseDWFuture = threadpool.submit(noiseDWThread);
        GhostThread ghostThread =
            new GhostThread(
                report.sourceImage, baseFolder, maxGhostImageSmallDimension, numGhostThreads);
        Future ghostFuture = threadpool.submit(ghostThread);
        ELAThread elaThread = new ELAThread(report.sourceImage, elaOutputfile);
        Future elaFuture = threadpool.submit(elaThread);
        BLKThread blkThread = new BLKThread(report.sourceImage, blkOutputfile);
        Future blkFuture = threadpool.submit(blkThread);
        MedianNoiseThread medianNoiseThread =
            new MedianNoiseThread(report.sourceImage, medianNoiseOutputFile);
        Future medianNoiseFuture = threadpool.submit(medianNoiseThread);
        GridsThread gridsThread =
            new GridsThread(report.sourceImage, gridsOutputFile, gridsInversedOutputFile);
        Future gridsFuture = threadpool.submit(gridsThread);
        //  GridsInversedThread gridsInversedThread = new
        // GridsInversedThread(report.sourceImage,gridsInversedOutputFile);
        //  Future gridsInversedFuture = threadpool.submit(gridsInversedThread);

        Long startTime = System.currentTimeMillis();
        MetadataExtractor metaExtractor;
        metaExtractor = new MetadataExtractor(report.sourceImage);
        JsonObject metadataReport = metaExtractor.metadataReport;
        metadataReport.addProperty("completed", true);
        report.metadataStringReport = metadataReport.toString();
        ds.save(report);

        GPSExtractor gpsExtractor = new GPSExtractor(metadataReport);
        gpsReport.completed = true;
        gpsReport.exists = gpsExtractor.exists;
        gpsReport.latitude = gpsExtractor.latitude;
        gpsReport.longitude = gpsExtractor.longitude;
        report.gpsReport = gpsReport;
        ds.save(report);

        ThumbnailReport thumbnail = new ThumbnailReport();
        ThumbnailExtractor thumbnailExtractor;
        thumbnailExtractor = new ThumbnailExtractor(report.sourceImage);
        thumbnail.numberOfThumbnails = thumbnailExtractor.numberOfThumbnails;
        File thumbFile = null;
        for (int thumbInd = 0; thumbInd < thumbnailExtractor.numberOfThumbnails; thumbInd++) {
          thumbFile = new File(baseFolder, "Thumbnail" + String.valueOf(thumbInd) + ".png");
          ImageIO.write(thumbnailExtractor.thumbnails.get(thumbInd), "png", thumbFile);
          thumbnail.thumbnailList.add(thumbFile.getCanonicalPath());
        }

        report.thumbnailReport = thumbnail;
        ds.save(report);

        while (!dqFuture.isDone()
            | !noiseDWFuture.isDone()
            | !ghostFuture.isDone()
            | !elaFuture.isDone()
            | !blkFuture.isDone()
            | !medianNoiseFuture.isDone()
            | !gridsFuture.isDone()) {
          Thread.sleep(100); // sleep for 1 millisecond before checking again
          if (dqFuture.isDone() & !dqSaved) {
            report.dqReport = (dqReport) dqFuture.get();
            dqSaved = true;
            ds.save(report);
            System.out.println("dqReport Done");
          }
          if (noiseDWFuture.isDone() & !noiseDWSaved) {
            report.dwNoiseReport = (DWNoiseReport) noiseDWFuture.get();
            noiseDWSaved = true;
            ds.save(report);
            System.out.println("DWNoiseReport Done");
          }
          if (ghostFuture.isDone() & !ghostSaved) {
            report.ghostReport = (GhostReport) ghostFuture.get();
            ghostSaved = true;
            ds.save(report);
            System.out.println("Ghost Done");
          }
          if (elaFuture.isDone() & !elaSaved) {
            report.elaReport = (ELAReport) elaFuture.get();
            elaSaved = true;
            ds.save(report);
            System.out.println("elaReport Done");
          }
          if (blkFuture.isDone() & !blkSaved) {
            report.blockingReport = (BlockingReport) blkFuture.get();
            blkSaved = true;
            ds.save(report);
            System.out.println("blockingReport Done");
          }
          if (medianNoiseFuture.isDone() & !medianNoiseSaved) {
            report.medianNoiseReport = (MedianNoiseReport) medianNoiseFuture.get();
            medianNoiseSaved = true;
            ds.save(report);
            System.out.println("Median Noise Done");
          }
          if (gridsFuture.isDone() & !gridsSaved) {
            GridsBothReport gridsBothReport = (GridsBothReport) gridsFuture.get();
            report.gridsReport = gridsBothReport.gridsNormalReport;
            report.gridsReport.completed = true;
            report.gridsInversedReport = gridsBothReport.gridsInversedReport;
            report.gridsInversedReport.completed = true;
            gridsSaved = true;
            ds.save(report);
            System.out.println("GridsReport Done");
          }
          if ((System.currentTimeMillis() - startTime) > computationTimeoutLimit) {
            System.out.println("Computation timed out");
            outMessage = "TIMEDOUT";
            // kill if timeout
            dqFuture.cancel(true);
            noiseDWFuture.cancel(true);
            ghostFuture.cancel(true);
            blkFuture.cancel(true);
            medianNoiseFuture.cancel(true);
            elaFuture.cancel(true);
            gridsFuture.cancel(true);
            break;
          }
        }
        threadpool.shutdown();
      }
    } catch (Exception e) {
      threadpool.shutdown();
      e.printStackTrace();
    }
    report.status = "Done";
    ds.save(report);
    System.out.println("Will now close mongodb connection");
    mongoclient.close();
    return outMessage;
  }