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

import vo.parameter.*;

class SodaImpl implements Soda
{
   static final Logger LOGGER = Logger.getLogger("SodaImpl");

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


   public SodaImpl()
   {
      LOGGER.info("trace SodaImpl()");
      this.settings = Settings.getInstance();
      this.subsurveys = null;
   }


   public SodaImpl(Settings settings)
   {
      LOGGER.info("trace SodaImpl(settings)");
      this.settings = settings;
      this.subsurveys = null;
   }

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


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

      boolean has_overlap  = false;
      boolean pixels_valid = (pixels != null);

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

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

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

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

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

         if(has_result)
         {
            boundsString = new String(bos.toByteArray());

            boundsString = replaceWithGrid(boundsString, pos, band, time, pol);
            LOGGER.info("boundsString(with GRID): " + boundsString);

            has_overlap = !((boundsString != null) && boundsString.trim().isEmpty());

            if( !has_overlap )
            {
               throw new IllegalArgumentException(
                     "region in file does not overlap with region defined by SODA parameters");
            }
         }
         bos.close();

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

      if(has_overlap || pixels_valid)
      {
         /* cutout -> outputStream */

         String pixFilterString = pixels_valid ? pixels : boundsString;

         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] = pixFilterString;
         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);

         LOGGER.info("execCut exitValue: " + execCut.exitValue);

         boolean cut_successful = (execCut.exitValue == 0);

         if(!cut_successful)
         {
            throw new IllegalArgumentException("cut by pixels not completed for pixels : " + pixFilterString);
         }

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


   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.getMin();
         double vu = band.getMax();

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

      return region;
   }


   private String replaceWithGrid(String wcsBounds, Pos pos, Band band, Time time, Pol pol)
   {
      // remove end-of-line (was added by vlkb_ast.cpp: cout << ... << endl)
      String lineSeparator = System.lineSeparator();
      wcsBounds = wcsBounds.replace(lineSeparator, "");
      LOGGER.info("BOUNDS: " + wcsBounds);

      // replace in wcsBounds those bounds where pos,band,time or pol has system=GRID

      String[] substr = wcsBounds.split("(?=AXES)", 2);
      for(String ss : substr) LOGGER.info("boundsResult: " + ss);

      String boundsString = substr[0];
      boolean noOverlap = ((boundsString != null) && boundsString.trim().isEmpty());
      if(noOverlap)
      {
         boundsString = ""; // no overlap
      }
      else
      {
         String axesTypes = "";
         if(substr.length > 1)
         {
            axesTypes = substr[1].replace("AXES"," ").trim();
            LOGGER.info("AXES TYPES: " + axesTypes);

            String[] bnds  = normalize(boundsString);
            String[] types = normalize(axesTypes);
            // assert: bnds.length == types.length
            LOGGER.info("boundsCount: " + bnds.length  + " typesCount: " + types.length);

            if(bnds.length == types.length)
               boundsString = replaceBounds(bnds, types, pos, band);
         }
      }
      return boundsString;
   }

   private String replaceBounds(String[] bnds, String[] types, Pos pos, Band band)
   {
      int ix;
      for(ix=0; ix<bnds.length; ix++)
      {
         if( types[ix].equals("LON") && ((pos != null) && (pos.system == Pos.System.GRID)) )
         {
            bnds[ix] = pos.lonBoundsString();
         }
         else if(types[ix].equals("LAT") && ((pos != null) && (pos.system == Pos.System.GRID)))
         {
            bnds[ix] = pos.latBoundsString();
         }
         else if(types[ix].equals("BAND") && ((band != null) && (band.system == Band.System.GRID)))
         {
            bnds[ix] = band.boundsString();
         }
      }

      LOGGER.info("replaced: " + String.join(" ", bnds)) ;

      return "[" + String.join(" ", bnds)  + "]";
   }

   // MAKE SURE vlkb overlap returns space delimited bounds: [ a:b c:d ]
   // normalize: strip [,] if any, and split into array by space
   private String[] normalize(String spaceStr)
   {
      String other = spaceStr.replace("[","").replace("]","");
      LOGGER.info("normalize: " + other);
      return other.split("\\s+");
   }

}