
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.FileOutputStream;
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

class CutoutImpl implements Cutout
{
   static final Logger LOGGER = Logger.getLogger(DatasetsImpl.class.getName());

   private Settings    settings   = null;
//   private Subsurvey[] subsurveys = null;


   public CutoutImpl()
   {
      LOGGER.info("trace DatasetsImpl()");
      this.settings = Settings.getInstance();
   }


   public CutoutImpl(Settings settings)
   {
      LOGGER.info("trace DatasetsImpl(settings)");
      this.settings = settings;
   }

/*
   public DatasetsImpl(Settings settings, Subsurvey[] subsurveys)
   {
      LOGGER.info("trace DatasetsImpl(settings, subsurveys)");
      this.settings = settings;
      this.subsurveys = subsurveys;
   }
*/

   private String genRegionForVlkbOverlapCmd(Pos pos, Band band)
   {
      String region = "";

      if(pos != null)
      {
         String skySystem = pos.system.name();

         if(pos.shape.equals("CIRCLE"))
         {
            double l = pos.circle.lon;
            double b = pos.circle.lat;
            double r = pos.circle.radius;
            region = region + "skysystem=" + skySystem + "&l=" + String.valueOf(l) + "&b=" + String.valueOf(b)
               + "&r=" + String.valueOf(r);
         }
         else if(pos.shape.equals("RANGE"))
         {
            double l =  (pos.range.lon1 + pos.range.lon2)/2.0;
            double b =  (pos.range.lat1 + pos.range.lat2)/2.0;
            double dl = (pos.range.lon2 - pos.range.lon1);
            double db = (pos.range.lat2 - pos.range.lat1);
            region = region + "skysystem=" + skySystem + "&l=" + String.valueOf(l) + "&b=" + String.valueOf(b)
               + "&dl=" + String.valueOf(dl) + "&db=" + String.valueOf(db);
         }
         else 
         {
            LOGGER.info("FIXME here Exception: POLYGON not supported or pos.shape invalid: " + pos.shape);
         }

      }

      if(band != null)
      {
         String specSystem = band.system.name();
         double vl = band.wavelength[0];
         double vu = band.wavelength[1];

         region =region + "specsystem=" + specSystem + "&vl=" + String.valueOf(vl) + "&vu=" + String.valueOf(vu);
      }

      return region;
   }

   public void doStream(String relPathname, int hdunum, Pos pos, Band band, Time time, Pol pol,
         OutputStream outputStream)  throws IOException, InterruptedException
      {
         Instant start = Instant.now();

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

         //String coordString = genRegionForVlkbOverlapCmd(pos, band);
         JsonEncoder jReq = new JsonEncoder();
         jReq.add(pos);
         jReq.add(band);
         jReq.add(time);
         jReq.add(pol);
         String coordString = jReq.toString();
         LOGGER.info("coordString: " + coordString);

         String absPathname = settings.fitsPaths.surveys() + "/" + relPathname;

         /* calc bounds */

         String[] cmdBounds = new String[4];
         cmdBounds[0] = "/usr/local/bin/vlkb";
         cmdBounds[1] = "overlap";
         cmdBounds[2] = absPathname;
         cmdBounds[3] = coordString;

         ExecCmd execBounds = new ExecCmd();
         execBounds.doRun(bos, cmdBounds);
         LOGGER.info("execBounds exitValue: " + execBounds.exitValue);

         bos.close();

         boolean has_result = (execBounds.exitValue == 0);

         Instant boundsDone = Instant.now();
         LOGGER.info("EXECTIME boundsDone: " + Duration.between(start, boundsDone));

         if(has_result)
         {
            String boundsString = new String(bos.toByteArray());
            // remove end-of-line (was added by vlkb_ast.cpp: cout << ... << endl)
            String lineSeparator = System.lineSeparator();
            boundsString = boundsString.replace(lineSeparator, "");
            LOGGER.info("BOUNDS: " + boundsString);

            if((boundsString != null) && boundsString.trim().isEmpty())
            {
               throw new IllegalArgumentException(
                     "region in file does not overlap with region defined by SODA parameters");
            }
            else
            {
               /* cutout -> outputStream */

               String[] cmdCut = new String[6];
               cmdCut[0] = "/usr/local/bin/vlkb";
               cmdCut[1] = "imcopy";
               cmdCut[2] = absPathname;
               cmdCut[3] = String.valueOf(hdunum-1);
               cmdCut[4] = boundsString;
               cmdCut[5] = settings.fitsPaths.cutouts();

               if(outputStream == null)
                  LOGGER.info("supplied outputStream for cut-file is null");

               ExecCmd execCut = new ExecCmd();
               execCut.doRun(outputStream, cmdCut);

               Instant cutDone = Instant.now();
               LOGGER.info("EXECTIME    cutDone: " + Duration.between(start, cutDone));
            }
         }
         else
         {
            throw new IllegalArgumentException(
                  "overlap computation could not be completed with the given arguments");
         }
      }


   public CutResult doFile(String relPathname, int hdunum,
         Pos pos, Band band, Time time, Pol pol,
         boolean countNullValues, FitsCard[] extraCards)
         throws IOException, InterruptedException
      {
         LOGGER.info("trace: " + pos.toString() );

         CutResult cutResult = new CutResult();

         if(settings.amqpConn.isHostnameEmpty())
         {
            LOGGER.info("Using doStream() to local file");

            String absSubimgPathname = settings.fitsPaths.cutouts()
               + "/" + generateSubimgPathname(relPathname, hdunum);

            LOGGER.info("Uses local filename : " + absSubimgPathname);

            OutputStream fileOutputStream = new FileOutputStream( new File(absSubimgPathname) );

            doStream(relPathname, hdunum, pos, band, time, pol, fileOutputStream);

            // engine returns absPathname see common/cutout.cpp::do_cutout_file()
            cutResult.filename = absSubimgPathname;
            cutResult.filesize = Files.size(Paths.get(absSubimgPathname));

            if(countNullValues)
            {
               LOGGER.info("NullValuesCount not implemented when used with Cutout::doStream()");
            }
         }
         else
         {
            LOGGER.info("Using AMQP");

            JsonEncoder jReq = new JsonEncoder();
            jReq.add(relPathname, hdunum);
            jReq.add(pos);
            jReq.add(band);
            jReq.add(time);
            jReq.add(pol);
            jReq.add(countNullValues);
            jReq.add(extraCards);

            String outJson = doRpc( jReq.toString() );

            cutResult = JsonDecoder.responseFromCutoutJson( outJson );
         }

         return cutResult;
      }



   private String doRpc(String InStr)
   {
      final String userName = "guest";
      final String password = "guest";
      // FIXME move these to Settings

      RpcOverAmqp rpc = new RpcOverAmqp(
            userName, password,
            settings.amqpConn.hostName(),
            settings.amqpConn.portNumber(),
            settings.amqpConn.routingKey());

      rpc.initConnectionAndReplyQueue();

      String OutStr = null;

      try
      {
         LOGGER.info("Sent request : " + InStr);
         OutStr = rpc.callAndWaitReply(InStr);
         LOGGER.info("Got response : " + OutStr);
      }
      catch  (Exception e)
      {
         e.printStackTrace();
      }
      finally
      {
         try
         {
            rpc.close();
         }
         catch (Exception ignore)
         {
            LOGGER.info("ignoring exception on rpc.close():" + ignore.getMessage());
         }
      }

      return OutStr;
   }


   private  String generateSubimgPathname(String relPathname, int hdunum)
   {
      String cutfitsname = "vlkb-cutout";

      Instant instant = Instant.now() ;
      String timestamp = instant.toString().replace(":","-").replace(".","_");

      String tempPathname1 = relPathname.replaceAll("/","-");
      String tempPathname2 = tempPathname1.replaceAll(" ","_");

      if(hdunum == 1)
      {
         return cutfitsname + "_" + timestamp + "_" + tempPathname2;
      }
      else
      {
         String extnum = "EXT" + String.valueOf(hdunum-1);
         return cutfitsname + "_" + timestamp + "_" + extnum + "_" + tempPathname2;
      }
   }

}

