
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 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 );
         mCutResult = doCompressCutFiles( cutResultArr );

         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)
      throws IOException, InterruptedException
   {
      String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());

      final String tgzFileName = settings.fitsPaths.cutouts() + "/mcutout_" + timestamp + ".tar.gz";
/*
      StringBuilder sb = new StringBuilder();
      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);
            sb.append(" " + p.getFileName().toString());
         }
      }

      String[] cmd = new String[6];
      cmd[0] = "/bin/tar";
      cmd[1] = "cfz";
      cmd[2] = tgzFileName;
      cmd[3] = "-C";
      cmd[4] = settings.fitsPaths.cutouts();
      cmd[5] = sb.toString();

      LOGGER.info("CMD: " + String.join(" ",cmd));

      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      if(bos == null)
         throw new AssertionError("byte output stream for bounds was not created, is null");

      ExecCmd exec = new ExecCmd();
      exec.doRun(bos, cmd);
      LOGGER.info("exec tar.gz exitValue: " + exec.exitValue);

      boolean has_result = (exec.exitValue == 0);
*/
      List<Path> paths = new ArrayList<Path>();

      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);

      // FIXME add response.json to tar.gz, add nanosecs to timestamp

      return mCutResult;
   }


   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;



*/
   public 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();
         }

      }

}

