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;

   // constructors
   Coord(Map<String, String[]> params)
   {
      String id     = getFirstValue(params, "ID");
      String pubdid = getFirstValue(params, "pubdid");

      if(id != null)
         parseSoda(params);
      else
         parseVlkb(params);

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


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

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


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


         String skySystem = Parser.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 = Parser.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;
      }
   }


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


   Coord(String lon, String lat, String radius, String velLow, String velUp)
   {
      try
      {
         this.skySystem = "GALACTIC";
         this.lon = Double.parseDouble(lon);
         this.lat = Double.parseDouble(lat);
         this.radius = Double.parseDouble(radius);
      }
      catch(Exception e)
      {
         throw new IllegalArgumentException("lon and lat are mandatory: " + e.getMessage());
      }

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

      this.shape = "CIRCLE";

      if((velLow != null) && (velUp != null))
      {
         this.vel_low = Double.parseDouble(velLow);
         this.vel_up  = Double.parseDouble(velUp);
         this.vel_type  = "1"; // VELO + LSRK
         //this.vel_type  = "2"; // WAVE + Barycentric
         this.vel_valid = true;
      }
      else
      {
         this.vel_valid = false;
      }
   }



   Coord(double lon, double lat, double radius)
   {
      if(radius < 0.0)
         throw new IllegalArgumentException("radius must be bigger than zero: " + radius); 

      this.skySystem = "GALACTIC";
      this.lon = lon;
      this.lat = lat;
      this.radius = radius;
      this.shape  = "CIRCLE";
      this.vel_valid = false;
   }

   Coord(double lon, double lat, double dlon, double dlat)
   {
      if((dlon < 0.0) || (dlat < 0.0))
         throw new IllegalArgumentException("both dlon and dlat must be bigger than zero: " + dlon + " " + dlat); 

      this.skySystem = "GALACTIC";
      this.lon  = lon;
      this.lat  = lat;
      this.dlon = dlon;
      this.dlat = dlat;
      this.shape = "RECT";
      this.vel_valid = false;
   }

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

   // spectral axis

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

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



   // FIXME separate keywords into dictionary key-string (LON->"l" LAT->"b" SKYSYSTEM->"skysystem")
   // to be part of api/QueryStringParams.java
   String toQueryString()
   {
      StringBuilder sb = new StringBuilder();

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

      switch(shape)
      {
         case "CIRCLE" : sb.append("&amp;r="  + radius   );
                         break;//   writer.println("<r>"+String.valueOf(radius)+"</r>"); break;
         case "RECT"   :
                         sb.append("&amp;dl=" + dlon  );
                         sb.append("&amp;db=" + dlat  );
                         //writer.println("<dl>"+String.valueOf(dlon)+"</dl>");
                         //writer.println("<db>"+String.valueOf(dlat)+"</db>");
                         break;
         default:
                         // ERROR internal err FIXME  writer.println("<shape> unknown shape: "+ shape +" </shape>");
      }

      if(vel_valid)
      {
         sb.append("&amp;vl=" + vel_low);
         sb.append("&amp;vu=" + vel_up );
         sb.append("&amp;vt=" + vel_type );
         //         writer.println("<vl>"   + String.valueOf(vel_low)  +"</vl>");
         //         writer.println("<vu>"   + String.valueOf(vel_up)   +"</vu>");
         //         writer.println("<vtype>"+ vel_type                 +"</vtype>");
      }

      return sb.toString();
   }


   String toVoQueryString()
   {
      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:
            ;// ERROR internal err FIXME  writer.println("<shape> unknown shape: "+ shape +" </shape>");
      }

      return sb.toString();
   }








}
