
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

import java.time.Instant;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.file.StandardOpenOption;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import java.time.*;// Timestamp in cut-filename
import java.io.ByteArrayOutputStream; // for SODA direct streaming doSubimgStream

// tar.gz compress
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import java.io.BufferedOutputStream;
import java.util.Date;
import java.text.SimpleDateFormat;
import org.json.simple.JSONArray;

import vo.parameter.*;

class DatasetsCli implements Datasets
{
   static final Logger LOGGER = Logger.getLogger("DatasetsCli");

   private Settings    settings   = null;
   private Subsurvey[] subsurveys = null;
   private Cutout      cutout     = null;

   public DatasetsCli()
   {
      LOGGER.info("trace DatasetsCli()");
      this.settings = Settings.getInstance();
      cutout = new CutoutImpl(settings, subsurveys);
   }


   public DatasetsCli(Settings settings)
   {
      LOGGER.info("trace DatasetsCli(settings)");
      this.settings = settings;
      this.cutout = new CutoutImpl(settings, subsurveys);
   }


   public DatasetsCli(Settings settings, Subsurvey[] subsurveys)
   {
      LOGGER.info("trace DatasetsCli(settings, subsurveys)");
      this.settings = settings;
      this.subsurveys = subsurveys;
      this.cutout = new CutoutImpl(settings, subsurveys);
   }




   public CutResult doMerge(String[] idArr, Coord coord, boolean countNullValues)
         throws FileNotFoundException, IOException
      {
         LOGGER.info("trace doMerge by CLI is NOT IMPLEMENTED (only by AMQP)");

         return new CutResult();
      }



   public MCutResult doMCutout(String jdlJson)
      throws IOException, InterruptedException
      {
         LOGGER.info("trace");

         MCutResult mCutResult;

         CutArgs[] cutArgsArr = CutArgs.parseCutArgsArr(jdlJson);
         MCutResult.Cut[] cutResultArr = doCutouts( cutArgsArr );
         String respJsonString = genResponseJson( cutResultArr );
         mCutResult = doCompressCutFiles( cutResultArr, respJsonString );

         return mCutResult;
      }



   private MCutResult.Cut[] doCutouts(CutArgs[] cutArgsArr)
   {
      LOGGER.info("trace, count of cuts : " + String.valueOf( cutArgsArr.length ) );

      List<MCutResult.Cut> cutResList = new ArrayList<MCutResult.Cut>();

      int ix = 0;
      for(CutArgs cutArgs: cutArgsArr)
      {
         MCutResult.Cut cut = doFileByIdWithErr(cutArgs.id,
               cutArgs.pos, cutArgs.band, cutArgs.time,  cutArgs.pol,  cutArgs.pixels,
               cutArgs.countNullValues,  null);//cutArgs.extraCards);

         cut.index = ix++;

         LOGGER.info("cut" + String.valueOf(cut.index) + " : " + cut.content);

         cutResList.add(cut);
      }

      return cutResList.toArray(new MCutResult.Cut[0]);
   }


   // FIXME implement similar for Merge: MCutResult = call-Montage-demosaic-sequence(cutResultArr)
   private MCutResult doCompressCutFiles(MCutResult.Cut[] cutArr, String respJsonString)
         throws IOException, InterruptedException
      {
         Instant instant = Instant.now();
         String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "_" + instant.getNano();
 
         final String tgzFileName = settings.fitsPaths.cutouts() + "/mcutout_" + timestamp + ".tar.gz";

         // generate response-*.json with timestamp
         String respJsonPathname = settings.fitsPaths.cutouts() + "/response_" + timestamp + ".json";
         try (PrintWriter out = new PrintWriter(respJsonPathname))
         {
            out.print(respJsonString);
         }

         List<Path> paths = new ArrayList<Path>();

         paths.add(Paths.get(respJsonPathname));

         for(MCutResult.Cut cut : cutArr)
         {
            LOGGER.info("cut-id"+ String.valueOf(cut.index) + " -> " + cut.content);
            if(cut.contentType == MCutResult.Cut.ContentType.FILENAME)
            {
               Path p = Paths.get(cut.content);
               paths.add(p);
            }
         }

         Path output = Paths.get(tgzFileName);
         createTarGzipFiles(paths, output);

         MCutResult mCutResult = new MCutResult();
         mCutResult.cutResArr = cutArr;
         mCutResult.fileName = tgzFileName;
         mCutResult.fileSize = Files.size(output);

         return mCutResult;
      }


   private String genResponseJson(MCutResult.Cut[] cutArr)
   {
      List<Path> paths = new ArrayList<Path>();

      JSONArray jArr = new JSONArray();

      for(MCutResult.Cut cut : cutArr)
      {
         jArr.add(cut.toJsonObject());
      }

      return jArr.toString();
   }



   private MCutResult.Cut doFileByIdWithErr(String id, Pos pos, Band band, Time time, Pol pol, String pixels,
         boolean countNullValues, Subsurvey[] subsurveys)
   {
      LOGGER.info("trace");

      MCutResult mCutResult = new MCutResult();
      MCutResult.Cut cut = mCutResult.new Cut(/* FIXME eventually add here new Inputs(id, pos,..) */);

      try
      {
         CutResult cutResult = cutout.doFileById(id,
               pos,  band, time,  pol, pixels,
               countNullValues,  subsurveys);

         cut.content     = cutResult.fileName;
         cut.contentType = MCutResult.Cut.ContentType.FILENAME;
      }
      catch(MultiValuedParamNotSupported ex) 
      {
         cut.content = "MultiValuedParamNotSupported: " + ex.getMessage();
         cut.contentType = MCutResult.Cut.ContentType.BAD_REQUEST;
         LOGGER.info(cut.content);
      }
      catch(IllegalArgumentException ex) 
      {
         cut.content = "IllegalArgumentException: " + ex.getMessage();
         cut.contentType = MCutResult.Cut.ContentType.BAD_REQUEST;
         LOGGER.info(cut.content);
      }
      catch(Exception ex) 
      {
         cut.content     = "Exception: " + ex.getMessage();
         cut.contentType = MCutResult.Cut.ContentType.SERVICE_ERROR;
         LOGGER.info(cut.content);
         ex.printStackTrace();
      }

      return cut;
   }



/*
      <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-compress</artifactId>
      <version>1.20</version>
      </dependency>

      import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
      import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
      import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
*/
   private static void createTarGzipFiles(List<Path> paths, Path output)
         throws IOException
      {
         try (OutputStream fOut = Files.newOutputStream(output);
               BufferedOutputStream buffOut = new BufferedOutputStream(fOut);
               GzipCompressorOutputStream gzOut = new GzipCompressorOutputStream(buffOut);
               TarArchiveOutputStream tOut = new TarArchiveOutputStream(gzOut))
         {
            for (Path path : paths)
            {
               if (!Files.isRegularFile(path))
               {
                  throw new IOException("Must be regular file, but was : " + path.toString());
               }

               TarArchiveEntry tarEntry = new TarArchiveEntry(
                     path.toFile(),
                     path.getFileName().toString());

               tOut.putArchiveEntry(tarEntry);

               // copy file to TarArchiveOutputStream
               Files.copy(path, tOut);

               tOut.closeArchiveEntry();
            }

            tOut.finish();
         }
      }

}

