
import java.util.logging.Logger;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.*;
 

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

 
import java.nio.charset.Charset;


class FormatResponseWrapper extends HttpServletResponseWrapper
{
   AuthPolicy auth;

   String[] pubdidArr;



   public FormatResponseWrapper(HttpServletResponse response)
   {
      super(response);
      auth = null;
   }

   public void setPubdidArr(String[] pubdidArr) { this.pubdidArr = pubdidArr; }
   public String[] getPubdidArr() { return this.pubdidArr; }

   public void setAuth(AuthPolicy auth)
   {
     // assert (this.search.inputs.auth != null);
      this.auth = auth;
   }
}





public class FormatResponseFilter implements Filter
{
   private static final Logger LOGGER = Logger.getLogger("FormatResponseFilter");
   private static final FormatResponseSettings settings = FormatResponseSettings.getInstance("formatresponsefilter.properties");

   final String RESPONSE_ENCODING = "UTF-8";
   protected Subsurvey[] dbSubsurveyArr  = null;
   private String reqQueryString;


   @Override
   public void init(FilterConfig filterConfig) throws ServletException
   {
      LOGGER.info("trace");

      String surveysAbsPathname = settings.serviceUrls.surveysAbsPathname();
      LOGGER.info("Loading metadata from: " + surveysAbsPathname);
      dbSubsurveyArr = Subsurvey.loadSubsurveys(surveysAbsPathname);
      LOGGER.info("Surveys: loaded metadata for " + dbSubsurveyArr.length + " known surveys");
      LOGGER.info("Default charset: " + Charset.defaultCharset());
   }


   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
   {
      LOGGER.info("trace");
      LOGGER.info("REQUEST START =============================================================================================");
 
      Map<String, String[]> params = request.getParameterMap();
      for(String keys : params.keySet()) LOGGER.info("Params: " + keys);
      Coord       coord           = new Coord(params);
      SubsurveyId subsurveyId     = new SubsurveyId(params);
      // FIXME add invalid param excpetions 

      FormatResponseWrapper responseWrapper = new FormatResponseWrapper((HttpServletResponse) response);

      chain.doFilter(request, responseWrapper);

      long startTime_msec = System.currentTimeMillis();

      PrintWriter responseWriter = ((HttpServletResponse)response).getWriter();

      if (true)
         //if (responseWrapper.getContentType().contains("text/plain"))
      {

         Dataset[] datasetArr = getOutputData(
               responseWrapper.getPubdidArr(),
               coord,// VLKB: calc ovelrap-code in Spectrum
               settings.serviceUrls.cutoutUrl());

         SearchOutputData searchOutputData = SearchOutputData.marshall(
               datasetArr,
               responseWrapper.auth,
               coord, // VLKB: calc Vertices
               subsurveyId,
               settings.serviceUrls.mergeUrl(),
               dbSubsurveyArr);

         response.setCharacterEncoding(RESPONSE_ENCODING);
         final String respFormat = settings.serviceUrls.responseFormat();
         LOGGER.info("responseFormat: " + respFormat);

         if(respFormat.equals("application/x-vlkb+xml"))
         {
            response.setContentType("application/xml");
            boolean showDuration = true;
            XmlSerializer.serializeToLegacyResults(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
         }
         else if(respFormat.equals("application/x-votable+xml"))
         {
            response.setContentType("application/xml");
            boolean showDuration = false;
            XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
         }
         else
         {
            ; // FIXME throws wrong setting-file param: batter convert string to enum and throw 'unrecoginzed respFormat' then;
            // here use switch with enums and switch-default: say error: 'unsupported respFromat' type
         }

         responseWriter.close();
      }
      LOGGER.info("REQUEST END   =============================================================================================");
   }



   @Override
   public void destroy()
   {
      LOGGER.info("trace");
   }


   ///////////////////////////////////////////////////////////////////
   // collect output data from DB


   /* FIXME type needed in DbPSearch */
   static class ObsCore
   {
      String dataproduct_type;
      Integer calib_level;
      String obs_collection;
      String obs_id;
      String obs_publisher_did;

      String access_url;
      String access_format;
      Long access_estsize;

      String target_name;

      Double s_ra, s_dec, s_fov;
      String s_region;
      Long s_xel1, s_xel2;
      Double s_resolution;

      Double t_min, t_max, t_exptime, t_resolution;
      Long t_xel;

      Double em_min,  em_max, em_res_power;
      Long em_xel;
      boolean em_valid;

      String o_ucd;

      String pol_states;
      Long   pol_xel;

      String facility_name;
      String instrument_name;

      String vertices_str;
      boolean inputInsideDb;
      boolean dbInsideInput;

   }



   private Dataset[] getOutputData(String[] pubdidArr, Coord coord, String fitsRemotePath)
   {
      LOGGER.info("trace");

      DbPSearch dbps;
      synchronized(DbPSearch.class)
      {
         dbps = new DbPSearch();
      }

      FormatResponseFilter.ObsCore[] obsCoreArr = dbps.queryOutputData(pubdidArr, coord);

      return convert(obsCoreArr, coord, fitsRemotePath);
   }





   private Dataset[] convert(FormatResponseFilter.ObsCore[] obsCoreArr, Coord coord, String fitsRemotePath)
   {
      List<Dataset> datasetList  = new ArrayList<Dataset>();

      for(FormatResponseFilter.ObsCore obsCore : obsCoreArr)
      {
         Dataset dataset = new Dataset();

         dataset.obsCore = obsCore;

         dataset.subsurvey_id   = obsCore.obs_collection;
         dataset.overlapCodeSky = convertToOverlapCodeSky(obsCore.inputInsideDb, obsCore.dbInsideInput);
         dataset.overlapCodeVel = convertToOverlapCodeVel(coord, obsCore.em_valid, obsCore.em_min, obsCore.em_max);
         dataset.overlapCode    = convertToOverlapCode(dataset.overlapCodeSky, dataset.overlapCodeVel);
         dataset.dataType       = obsCore.dataproduct_type;
         dataset.publisherDid   = obsCore.obs_publisher_did;

         dataset.access.accessFileUrl   = obsCore.access_url;
         dataset.access.accessCutoutUrl = fitsRemotePath + "?ID=" + dataset.publisherDid + "&" + coord.toVoQueryString();
         //dataset.access.accessCutoutUrl = fitsRemotePath + "?pubdid=" + dataset.publisherDid + "&amp;" + queryString;
         dataset.access.accessMosaicUrl  = null;

         dataset.vertices_deg = convertToVertices(obsCore.vertices_str);

         datasetList.add(dataset);
      }

      return datasetList.toArray(new Dataset[0]);
   }


   private int convertToOverlapCodeSky(boolean inpInDb, boolean dbInInp)
   {
      if(!inpInDb && !dbInInp) return 4; // parial overlap
      else if( inpInDb && !dbInInp) return 3; // input region completely inside fits-datacube
      else if(!inpInDb &&  dbInInp) return 2; // datacube completely inside input-region
      else return 5; // exact match: both inpInDb dbInInp are true
   }


   private int convertToOverlapCodeVel(Coord coord, boolean v_valid, double v_min, double v_max)
   {
      if(coord.vel_valid && v_valid)
      {
         if(coord.vel_type.equals("1"))
         {
            // FIXME assert coord: vel_min <= vel_max
            // FIXME assert cube:  v_min   <= v_max

            boolean dbInInp = (coord.vel_low <= v_min) && (v_min <= coord.vel_up)
               && (coord.vel_low <= v_max) && (v_max <= coord.vel_up);

            boolean inpInDb = (v_min <= coord.vel_low) && (coord.vel_low <= v_max)
               && (v_min <= coord.vel_up ) && (coord.vel_up  <= v_max);

            return convertToOverlapCodeSky(inpInDb, dbInInp);

         }
         else ;// FIXME other v_type NotImplemented yet
      }

      return -1; // FIXME use enums; meaning: overlap code in velocity not applicable --> dont print in XML
   }



   private int convertToOverlapCode(int ovcSky, int ovcVel)
   {
      if(ovcVel == -1) return ovcSky; // 2D images
      else if(ovcSky == ovcVel) return ovcSky;
      else return 4;
   }


   private Dataset.Vertices convertToVertices(String polygon)
   {
      final double RAD2DEG = 180.0 / Math.PI;

      Dataset.Vertices vert = new Dataset.Vertices();

      polygon = polygon.replace("("," ");
      polygon = polygon.replace(")"," ");
      polygon = polygon.replace("{"," ");
      polygon = polygon.replace("}"," ");

      String[] verts = polygon.split(",");

      vert.lon[0] = RAD2DEG * Double.parseDouble(verts[0]);
      vert.lat[0] = RAD2DEG * Double.parseDouble(verts[1]);
      vert.lon[1] = RAD2DEG * Double.parseDouble(verts[2]);
      vert.lat[1] = RAD2DEG * Double.parseDouble(verts[3]);
      vert.lon[2] = RAD2DEG * Double.parseDouble(verts[4]);
      vert.lat[2] = RAD2DEG * Double.parseDouble(verts[5]);
      vert.lon[3] = RAD2DEG * Double.parseDouble(verts[6]);
      vert.lat[3] = RAD2DEG * Double.parseDouble(verts[7]);

      return vert;
   }


}
