import java.util.logging.Logger;
import java.util.Map;
import java.io.PrintWriter;

class Coord
{
   private static final Logger LOGGER = Logger.getLogger(Coord.class.getName());

   String skySystem; // FIXME make enum

   // center
   double lon;
   double lat;

   // extent  < EXT_LIMIT = 4deg
   String shape; // FIXME enum CIRCLE | RECT 
   double radius;
   double dlon;
   double dlat;

   public boolean vel_valid;
   String vel_type; // FIXME make enum
   double vel_up;
   double vel_low;

   private boolean API_VLKB_legacy = false;

   Coord(Map<String, String[]> params)
   {
      String api_l = getFirstValue(params, "l");
      String api_b = getFirstValue(params, "b");

      API_VLKB_legacy = ((api_l != null) && (api_b != null));

      if(API_VLKB_legacy)
         parseVlkb(params);
      else
         parseSoda(params);

      LOGGER.info("Parse result: " + toQueryString());
   }


   protected void parseSoda(Map<String, String[]> params)
   {
      LOGGER.info("trace");

      try
      {
         String pubdid   = null;
         Coord  coord    = null;


         pubdid          = getFirstString(params, "ID");
         String[] circle = getFirstStringArray(params,"CIRCLE", " ", 3);
         String[] vel    = getFirstStringArray(params,"BAND", " ", 2);


         String skySystem = getFirstString(params, "skysystem"); // FIXME add sanity checks / use enum
         if(skySystem == null) skySystem = "ICRS";

         this.skySystem = skySystem;
         this.lon = Double.parseDouble(circle[0]);
         this.lat = Double.parseDouble(circle[1]);
         this.radius = Double.parseDouble(circle[2]);

         if(this.radius <= 0.0) throw new IllegalArgumentException("radius must be positive and not zero");
         this.shape = "CIRCLE";


         String specSystem = getFirstString(params, "specsystem");
         if(specSystem == null) specSystem = "2"; // 2=WAVE BARY
         if( (vel != null) && (vel.length >= 2) )
         {
            this.vel_type = specSystem; // FIXME add sanity checks / use enum

            if((vel[0] != null) && (vel[1] != null))
            {
               this.vel_low = Double.parseDouble(vel[0]);
               this.vel_up  = Double.parseDouble(vel[1]);
               this.vel_valid = true;
            }
            else
            {
               this.vel_valid = false;
            }
         }
      }
      catch (IllegalArgumentException illArg)
      {
         // response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request with incorrect parameters: " + illArg.getMessage());
         throw new IllegalArgumentException("Request with incorrect parameters: " + illArg.getMessage());
      }
   } 


   protected void parseVlkb(Map<String, String[]> params)
   {
      LOGGER.info("trace");

      // (l,b) is mandatory 

      this.skySystem = "GALACTIC";
      String lcl = getFirstValue(params, "l");
      String lcb = getFirstValue(params, "b");

      if((lcl == null) || (lcb == null))
         throw new IllegalArgumentException("parameters (l,b) are mandatory."); 

      lon = Double.parseDouble(lcl);
      lat = Double.parseDouble(lcb);

      // one of: radius r OR (dlon,dlat) is mandatory

      if(params.containsKey("r"))
      {
         String lcr = getFirstValue(params, "r");
         radius = Double.parseDouble(lcr);
         this.shape = "CIRCLE";
      }
      else if (params.containsKey("dl") && params.containsKey("db"))
      {
         String lcdl = getFirstValue(params, "dl");
         String lcdb = getFirstValue(params, "db");
         dlon = Double.parseDouble(lcdl);
         dlat = Double.parseDouble(lcdb);
         this.shape = "RECT";
      }
      else
         throw new IllegalArgumentException("area extent is mandatory: either radius r or (dl,db) must be given."); 

      // velocity is optional

      String cvlow = getFirstValue(params, "vl");
      String cvup  = getFirstValue(params, "vu");
      String cvtype = "1"; // VLKB: VELO LSRK

      vel_valid = (cvlow != null) && (cvup != null);

      if(vel_valid)
      {
         vel_low = Double.parseDouble(cvlow);
         vel_up  = Double.parseDouble(cvup);
         vel_type = cvtype;
      }
   }



   void setSkySystem(String skySystem) { this.skySystem = skySystem; }
   void setSpecSystem(String velType) { this.vel_type = velType; }

   void setVelocity(double vel_low, double vel_up, String vel_type)
   {
      this.vel_type  = vel_type;
      this.vel_low   = vel_low;
      this.vel_up    = vel_up;
      this.vel_valid = true;
   }





   // generate cutout/merge queryStrings

   String toQueryString()
   {
      if(API_VLKB_legacy)
         return toVlkbLegacyQueryString();
      else
         return toVoQueryString();
   }


   String toVlkbLegacyQueryString()
   {
      LOGGER.info("trace");

      StringBuilder sb = new StringBuilder();

      sb.append("skysystem=" + skySystem);
      sb.append("&l=" + lon );
      sb.append("&b=" + lat );

      switch(shape)
      {
         case "CIRCLE" : sb.append("&r="  + radius   );
                         break;
         case "RECT"   :
                         sb.append("&dl=" + dlon  );
                         sb.append("&db=" + dlat  );
                         break;
         default:
            LOGGER.info("Coord::toVlkbLegacyQueryString: unknown shape: " + shape);
      }

      if(vel_valid)
      {
         sb.append("&vl=" + vel_low);
         sb.append("&vu=" + vel_up );
         sb.append("&specsystem=" + vel_type );
      }

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

      return sb.toString();
   }


   String toVoQueryString()
   {
      LOGGER.info("trace");

      StringBuilder sb = new StringBuilder();

      sb.append("skysystem=" + skySystem);

      switch(shape)
      {
         case "CIRCLE" :
            sb.append("&POS=CIRCLE "  + lon + " " + lat + " " + + radius );
            break;

         case "RECT" :
            if(vel_valid)
               sb.append("&POS=RANGE="
                     + " " + (lon - dlon)  + " " + (lon + dlon)  
                     + " " + (lat - dlat)  + " " + (lat + dlat)  
                     + " " + vel_low  + " " + vel_up );

            else       
               sb.append("&POS=RANGE="
                     + " " + (lon - dlon)  + " " + (lon + dlon)  
                     + " " + (lat - dlat)  + " " + (lat + dlat) );
            break;

         default:
            LOGGER.info("Coord::toVoQueryString: unknown shape: " + shape);
      }

      if(vel_valid)
      {
         sb.append("&BAND= " + vel_low + " " + vel_up);
         sb.append("&specsystem=" + vel_type );
      }

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

      return sb.toString();
   }





   // utils

   public String toString()
   {
      String area = null;
      switch(shape)
      {
         case "CIRCLE" : area = String.valueOf(radius); break;
         case "RECT"   : area = dlon + ", " + dlat; break;
         default: // FIXME leave with exception
                         area = "err: " + shape;
      }

      String resourceSearchArea 
         = "(P; area) = (" + lon + ", " + lat + "; " + area + ") [deg]";

      return resourceSearchArea;
   }


   void toXML(PrintWriter writer)
   {
      // center is mandatory -> create no Coord if center not valid
      writer.println("<SkySystem>"+skySystem+"</SkySystem>");
      writer.println("<l>"+lon+"</l>");
      writer.println("<b>"+lat+"</b>");

      switch(shape)
      {
         case "CIRCLE" :  writer.println("<r>"+String.valueOf(radius)+"</r>"); break;
         case "RECT"   :
                          writer.println("<dl>"+String.valueOf(dlon)+"</dl>");
                          writer.println("<db>"+String.valueOf(dlat)+"</db>");
                          break;
         default:
                          writer.println("<shape> unknown shape: "+ shape +" </shape>");
      }
      if(vel_valid)
      {
         writer.println("<vl>"   + String.valueOf(vel_low)  +"</vl>");
         writer.println("<vu>"   + String.valueOf(vel_up)   +"</vu>");
         writer.println("<vtype>"+ vel_type                 +"</vtype>");
      }
   }



   // used in constructor and parseVlkb
   // if param present -> must have at least one value
   static private String getFirstValue(Map<String, String[]> map, String key)
   {
      String[] value = map.get(key);

      if(value == null)
         return null;

      if(value.length > 0)    // key-value present at least once: return first occurance
         return value[0].toString();
      else                    // length=0: no values present (array exists but is empty)
         throw new IllegalArgumentException("parameter " + key + " has no value."); 
   }


   // used in parseSoda
   String getFirstString(Map<String, String[]> params, String key)
   {
      String[] values = params.get(key);
      if (values == null) return null;

      if (values.length < 1)
         throw new IllegalArgumentException(key + " has no valid value");
      else
         return values[0];// FIXME if values[0] is null -> canot distinguish from key not found
   }


   String[] getFirstStringArray(Map<String, String[]> params, String key, String separator, int arrayLength)
   {
      String array = getFirstString(params, key);
      if (array == null) return null;

      String[] stringArray = array.split(separator);

      if(stringArray.length != arrayLength)
         throw new IllegalArgumentException(
               key + " parameter has incorrect number of elements ("
               + stringArray.length + " vs " + arrayLength + ") or incorrect separator used");

      return stringArray;
   }

}
