
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;

import vo.parameter.*;

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";
   final String DEFAULT_RESPONSEFORMAT = settings.defaults.responseFormat;
   final String DEFAULT_SKY_SYSTEM     = settings.defaults.skySystem;
   final String DEFAULT_SPEC_SYSTEM    = settings.defaults.specSystem;
   final String DEFAULT_TIME_SYSTEM = "MJD_UTC"; // FIXME take from confif file

   protected Subsurvey[] dbSubsurveyArr  = null;
   private String reqQueryString;

   protected void doUsageError(String message, PrintWriter printWriter)
   {
      printWriter.println("UsageError : " + message);
   }


   @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());
      LOGGER.info("DB: " + settings.dbConn.toString());
   }


   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
   {
      LOGGER.info("trace");
      LOGGER.info("REQUEST START =============================================================================================");
      long startTime_msec = System.currentTimeMillis();

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

      chain.doFilter(request, responseWrapper);

      String[] pubdidArr = responseWrapper.getPubdidArr();

      if ((pubdidArr != null) && (pubdidArr.length > 0))
      {
         PrintWriter responseWriter = ((HttpServletResponse)response).getWriter();

         Map<String, String[]> params = request.getParameterMap();
//         Coord       coord            = new Coord(params);
         SubsurveyId subsurveyId      = new SubsurveyId(params);
         // FIXME add invalid param excpetions -> params already parsed in servlet

         Coord coord = new Coord();
         coord.pos  = Pos.parsePos(params, DEFAULT_SKY_SYSTEM);
         coord.band = Band.parseBand(params, DEFAULT_SPEC_SYSTEM);
         coord.time = Time.parseTime(params, DEFAULT_TIME_SYSTEM);
         coord.pol  = Pol.parsePol(params);

         ObsCore[] obsCoreArr = queryObsCore(pubdidArr,
               coord); // VLKB: calc overlap-code for sky

         Dataset[] datasetArr = convert(obsCoreArr,
               coord, // VLKB: calc overlap-code for velocity
               settings.serviceUrls.cutoutUrl(),
               toQueryString(coord));

         SearchOutputData searchOutputData = SearchOutputData.marshall(datasetArr,
               subsurveyId,
               dbSubsurveyArr,
               settings.serviceUrls.mergeUrl(),
               toQueryString(coord));

         String respFormat;
         String respFormatReq[] = params.get("RESPONSEFORMAT");
         if(respFormatReq != null && (respFormatReq.length > 0) && !respFormatReq[0].isEmpty())
         {
            respFormat = respFormatReq[0];
            LOGGER.info("responseFormat(from request): " + respFormat);
         }
         else
         {
            respFormat = settings.serviceUrls.responseFormat();
            LOGGER.info("responseFormat(from settings): " + respFormat);
         }

         response.setCharacterEncoding(RESPONSE_ENCODING);

         if(respFormat.equals("application/x-vlkb+xml"))
         {
            response.setContentType("application/xml");
            boolean showDuration = true;
            XmlSerializer.serializeToLegacyResults(responseWriter, RESPONSE_ENCODING,
                  responseWrapper.auth, coord, subsurveyId, // <inputs/>
                  searchOutputData,
                  showDuration,startTime_msec);
         }
         else if(respFormat.startsWith("application/x-votable+xml"))
         {
            response.setContentType("application/xml");
            boolean showDuration = false;

            if(respFormat.contains("mode=bysubsurveys"))
               XmlSerializer.serializeToVoTableBySubsurveys(responseWriter, RESPONSE_ENCODING,
                     searchOutputData,showDuration,startTime_msec);
            else if(respFormat.contains("mode=bysurveys"))
               XmlSerializer.serializeToVoTableBySurveys(responseWriter, RESPONSE_ENCODING,
                     searchOutputData,showDuration,startTime_msec);
            else
               XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING,
                     searchOutputData,showDuration,startTime_msec);

         }
         else
         {
            final String errMsg = "Illegal response format request: " + respFormat;
            LOGGER.info(errMsg);
            response.setContentType("text/plain");
            doUsageError(errMsg, responseWriter);
            // FIXME set http err code 
         }

         responseWriter.close();
      }
      else
      {
         LOGGER.info("SearchServlet returned no ID's.");
      }

      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 FormatResponseFilter.ObsCore[] queryObsCore(String[] pubdidArr, Coord coord)//, String fitsRemotePath)
   {
      LOGGER.info("trace");

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

      return dbps.queryOutputData(pubdidArr, coord);
      //FormatResponseFilter.ObsCore[] obsCoreArr = dbps.queryOutputData(pubdidArr, coord);
      //return convert(obsCoreArr, coord, fitsRemotePath);
   }





   private Dataset[] convert(FormatResponseFilter.ObsCore[] obsCoreArr, Coord coord,
         String cutoutUrlRoot, String cutoutQueryString)
   {
      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 = cutoutUrlRoot + "?" + cutoutQueryString + dataset.publisherDid;
         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.band != null) && v_valid)
      {
         if(coord.band.system == Band.System.VELO_LSRK)
         {
            // FIXME assert coord: vel_min <= vel_max
            // FIXME assert cube:  v_min   <= v_max

            boolean dbInInp = (coord.band.getMin() <= v_min) && (v_min <= coord.band.getMax())
               && (coord.band.getMin() <= v_max) && (v_max <= coord.band.getMax());

            boolean inpInDb = (v_min <= coord.band.getMin()) && (coord.band.getMin() <= v_max)
               && (v_min <= coord.band.getMax() ) && (coord.band.getMax() <= 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;
   }



      // generate cutout/merge queryStrings
   private String toQueryString(Coord coord)
   {
      LOGGER.info("trace");

      StringBuilder sb = new StringBuilder();

      //sb.append("POSSYS=" + coord.pos.system.toString());
      switch(coord.pos.shape)
      {
         case CIRCLE:
            sb.append("POS=" + coord.pos.circle.toString());
            break;

         case RANGE:
            sb.append("POS=" + coord.pos.range.toString());
            break;

         case POLYGON:
            sb.append("POS=" + coord.pos.polygon.toString());
            break;

         default:
            LOGGER.info("Coord::toQueryString: unknown shape: " + coord.pos.shape.toString());
      }

      if(coord.band != null)
      {
         sb.append("&" + coord.band.toString());
         //sb.append("&BANDSYS=" + coord.band.system.toString() );
      }

      sb.append("&ID="); // FIXME id-value will be added in FormatResponseFilter

      return sb.toString();
   }



}
