import java.io.PrintWriter;

// VOTable
import uk.ac.starlink.table.*;// StarTable needed
import uk.ac.starlink.votable.*;// Writer needed

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.BufferedWriter;

import java.util.List;
import java.util.ArrayList;


final class XmlSerializer
{
   private XmlSerializer() {}

   // VOTable

   public static void serializeToVoTable(
         PrintWriter writer, String charEncoding,
         SearchOutputData searchOutputData,
         boolean showDuration, long startTime_msec) throws IOException
   {
      StarTable dstable = makeSearchResultsTable( searchOutputData.subsurveyArr );

      dstable.setParameter(new DescribedValue(
               new DefaultValueInfo( "subsurveyCount", Integer.class, "Count of subsurveys with found datacube(s)" ),
               searchOutputData.subsurveyArr.length ) );

      dstable.setParameter(new DescribedValue(
               new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
               searchOutputData.datacubeCount ) );

      BufferedWriter out = new BufferedWriter( writer );

      out.write("<?xml-stylesheet type='text/xsl' href='VOTable2XHTML.xsl'?>");
      out.write( "<VOTABLE version='1.1'>" );
      out.write( "<RESOURCE>" );
      out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );

      VOSerializer.makeSerializer( DataFormat.TABLEDATA, dstable ).writeInlineTableElement( out );

      out.write( "</RESOURCE>" );
      out.write( "</VOTABLE>" );
      out.flush();
   }

   private static StarTable makeSearchResultsTable(Subsurvey[] ssurv)
   {
      RowListStarTable astro = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_SUBSURVEY_COLINFO );

      for(Subsurvey subsurvey : ssurv)
      {
         for(Dataset dataset : subsurvey.datasetArr)
         {
            if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets

            astro.addRow( ObscoreExt.obscoreVlkbSubsurveyRow(dataset, subsurvey) );
         }
      }

      return astro;
   }


   public static void serializeToVoTableBySubsurveys(
         PrintWriter writer, String charEncoding,
         SearchOutputData searchOutputData,
         boolean showDuration, long startTime_msec) throws IOException
   {
      StarTable[] tables = makeSubsurveyTables( searchOutputData.subsurveyArr );
      BufferedWriter out = new BufferedWriter( writer );

      out.write( "<VOTABLE version='1.1'>" );
      out.write( "<RESOURCE>" );
      out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );
      for ( int i = 0; i < tables.length; i++ )
      {
         VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[ i ] ).writeInlineTableElement( out );
      }
      out.write( "</RESOURCE>" );
      out.write( "</VOTABLE>" );
      out.flush();
   } 

   private static StarTable[] makeSubsurveyTables(Subsurvey[] subsurveyArr)
   {
      StarTable[] ssurvTableArr = new StarTable[subsurveyArr.length]; 
      int ix = 0;

      for(Subsurvey subsurvey : subsurveyArr)
      {
         RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "subsurveyCount", Integer.class, "Count of subsurveys with found datacube(s)" ),
                  subsurveyArr.length ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
                  subsurvey.datasetArr.length ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "velocity_unit",  String.class, "Unit of velocity in FITS header" ),
                  subsurvey.vel_unit ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "survey",  String.class, "Survey name" ),
                  subsurvey.surveyname ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "species",  String.class, "Species" ),
                  subsurvey.species ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "transition",  String.class, "Transition" ),
                  subsurvey.transition ) );

         table.setParameter(new DescribedValue(
                  new DefaultValueInfo( "description",  String.class, "Reference description" ),
                  subsurvey.description ) );

         for(Dataset dataset : subsurvey.datasetArr)
         {
            if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets

            table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
         }

         ssurvTableArr[ix++] = table;
      }

      return ssurvTableArr;
   }




   public static void serializeToVoTableBySurveys(
         PrintWriter writer, String charEncoding,
         SearchOutputData searchOutputData,
         boolean showDuration, long startTime_msec) throws IOException
   {
      BufferedWriter out = new BufferedWriter( writer );

      out.write( "<VOTABLE version='1.1'>" );
      out.write( "<DESCRIPTION> " + searchOutputData.versionString + 
             " subsurvey count: " + searchOutputData.subsurveyArr.length + " </DESCRIPTION>" );

      if((searchOutputData.subsurveyArr != null) && (searchOutputData.subsurveyArr.length > 0))
      {
         Subsurvey[] subsurv = searchOutputData.subsurveyArr;

         // assumes ORDERED subsurveyArray: by surveyname

         List<StarTable> tableList = new ArrayList();
         String prevSurveyname;// = subsurv[0].surveyname.trim();
         int ix = 0;

         do
         {
            prevSurveyname = subsurv[ix].surveyname.trim();

            do
            {
               StarTable table = makeSubsurveyTable( subsurv[ix] );
               tableList.add(table);
               ix++;
            }
            while((ix < subsurv.length) && prevSurveyname.equals(subsurv[ix].surveyname.trim()));

            StarTable[] tables = tableList.toArray(new StarTable[0]);
            writeResourceSurvey(out, prevSurveyname, tables);
            tableList.clear();
         }
         while(ix < subsurv.length);

      }
      out.write( "</VOTABLE>" );
      out.flush();
   }

   private static void writeResourceSurvey(BufferedWriter out, String description, StarTable[] tables)
         throws IOException
      {
         out.write( "<RESOURCE>" );
         out.write( "<DESCRIPTION> " + description + " </DESCRIPTION>" );

         /* PLACEHOLDER FOR RESOURCE PARAM

            table.setParameter(new DescribedValue(
            new DefaultValueInfo("subsurveyCount",Integer.class,
            "Count of subsurveys with found datacube(s)" ),
            subsurveyArr.length ) );

            table.setParameter(new DescribedValue(
            new DefaultValueInfo( "survey",  String.class,
            "Survey name" ),
            subsurvey.surveyname ) );

*/
         for ( int i = 0; i < tables.length; i++ )
         {
            VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[i] ).writeInlineTableElement( out );
         }

         out.write( "</RESOURCE>" );
      }



   private static StarTable makeSubsurveyTable(Subsurvey subsurvey)
   {
      RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo("datacubeCount", Integer.class, "Count of all datacubes from VLKB-search" ),
               subsurvey.datasetArr.length ) );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo( "velocity_unit",  String.class, "Unit of velocity in FITS header" ),
               subsurvey.vel_unit ) );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo( "survey",  String.class, "Survey name" ),
               subsurvey.surveyname ) );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo( "species",  String.class, "Species" ),
               subsurvey.species ) );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo( "transition",  String.class, "Transition" ),
               subsurvey.transition ) );

      table.setParameter(new DescribedValue(
               new DefaultValueInfo( "description",  String.class, "Reference description" ),
               subsurvey.description ) );

      for(Dataset dataset : subsurvey.datasetArr)
      {
         if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets

         table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
      }

      return table;
   }


   // legacy

   public static void serializeToLegacyResults(
         PrintWriter writer, String charEncoding,
         AuthPolicy inputAuth, Coord inputCoord, SubsurveyId inputSubsurveyId,
         SearchOutputData searchOutputData,
         boolean showDuration, long startTime_msec)
   {
      writer.println("<?xml version=\"1.0\" encoding=\"" + charEncoding + "\" standalone=\"yes\"?>");
      writer.println("<results>");
      writer.println("<description> " + searchOutputData.description + " </description>");

      writer.println("<inputs>");
      if(inputSubsurveyId != null) writer.println(serialize(inputSubsurveyId));
      if(inputCoord       != null) writer.println(serialize(inputCoord));
      if(inputAuth        != null) writer.println(serialize(inputAuth));
      writer.println("</inputs>");

      writer.println("<msg> " + searchOutputData.versionString + " </msg>");
      writer.println("<DatacubeCount> " + searchOutputData.datacubeCount + " </DatacubeCount>");
      for(Subsurvey subsurvey : searchOutputData.subsurveyArr)
      {
         serialize(writer, subsurvey);
      }
      if(showDuration)
         writer.println("<duration unit=\"msec\">" + (System.currentTimeMillis() - startTime_msec) + "</duration>");
      writer.println("</results>");
   }

   private static String serialize(Coord coord)
   {
      StringBuilder xml = new StringBuilder();
      if(coord.pos != null)
      {
         xml.append("<SkySystem>"+coord.pos.system+"</SkySystem>");
         switch(coord.pos.shape)
         {
            case CIRCLE:
               xml.append("<l>" + String.valueOf(coord.pos.circle.lon) + "</l>");
               xml.append("<b>" + String.valueOf(coord.pos.circle.lat) + "</b>");
               xml.append("<r>" + String.valueOf(coord.pos.circle.radius)+"</r>"); break;
            case RANGE:
               xml.append("<l>" + String.valueOf((coord.pos.range.lon1 + coord.pos.range.lon2)/2.0) + "</l>");
               xml.append("<b>" + String.valueOf((coord.pos.range.lat1 + coord.pos.range.lat2)/2.0) + "</b>");
               xml.append("<dl>" + String.valueOf(coord.pos.range.lon2 - coord.pos.range.lon1) + "</dl>");
               xml.append("<db>" + String.valueOf(coord.pos.range.lat2 - coord.pos.range.lat1) + "</db>");
               break;
            default: // POLYGON was not used in VLKB-legacy -> let it fail with error
               xml.append("<shape> unknown shape: " + coord.pos.shape + " </shape>");
         }
      }

      if(coord.band != null)
      {
         xml.append("<vl>"   + String.valueOf(coord.band.getMin())  +"</vl>");
         xml.append("<vu>"   + String.valueOf(coord.band.getMax())   +"</vu>");
         xml.append("<vtype>"+ coord.band.system                 +"</vtype>");
      }

      return xml.toString();
   }

   private static String serialize(SubsurveyId subsurveyId)
   {
      StringBuilder xml = new StringBuilder();
      if(subsurveyId.surveyName  != null) xml.append("<SurveyName>"+subsurveyId.surveyName+"</SurveyName>");
      if(subsurveyId.species     != null) xml.append("<Species>"+subsurveyId.species+"</Species>");
      if(subsurveyId.transition  != null) xml.append("<Transition>"+subsurveyId.transition+"</Transition>");
      return xml.toString();
   }

   private static String serialize(AuthPolicy auth)
   {
      StringBuilder xml = new StringBuilder();
      xml.append("<AccessPolicy>" + auth.getAccessPolicy() + "</AccessPolicy>");
      String ug = auth.getUserGroupsAsString(" ");
      if(auth.getUserName() != null) xml.append("<UserName>" + auth.getUserName() + "</UserName>");
      if(ug            != null) xml.append("<GroupNames>" + ug + "</GroupNames>");
      return xml.toString();
   }


   private static void serialize(PrintWriter writer, Subsurvey subsurvey)
   {
      writer.println("<survey>");
      // replace with escape the XML-predefined entities:
      // <, >, &, %
      if(subsurvey.description != null)
      {
         subsurvey.description = subsurvey.description.replace("&","&amp;");
         subsurvey.description = subsurvey.description.replace("<","&lt;");
         subsurvey.description = subsurvey.description.replace(">","&gt;");
         subsurvey.description = subsurvey.description.replace("%","&#37;");
      }

      writer.println("<Description>"  + subsurvey.description + "</Description>");
      writer.println("<Survey>"       + subsurvey.surveyname  + "</Survey>");
      writer.println("<Species>"      + subsurvey.species     + "</Species>");
      writer.println("<Transition>"   + subsurvey.transition  + "</Transition>");
      writer.println("<RestFreq>");
      writer.println("<value>"        + subsurvey.rf + "</value>");
      writer.println("<unit>"         + "Hz"             + "</unit>"); // FIXME why was this needed? checj survey_populate,csv
      writer.println("</RestFreq>");
      writer.println("<VelocityUnit>" + subsurvey.vel_unit + "</VelocityUnit>");

      for(Dataset dataset : subsurvey.datasetArr)
      {
         writer.println(serialize(dataset));
      }
      writer.println("</survey>");
   }

   private static String serialize(Dataset.Access access)
   {
      StringBuilder xml = new StringBuilder();

      xml.append("<Access>");

      if(access.accessFileUrl != null)
         xml.append("<URL type=\"file\">" + access.accessFileUrl + "</URL>");

      if(access.accessCutoutUrl != null)
         xml.append("<URL type=\"cutout\">" + access.accessCutoutUrl.replaceAll("&","&amp;") + "</URL>");

      if(access.accessMosaicUrl != null)
         xml.append("<URL type=\"mosaic\">" + access.accessMosaicUrl.replaceAll("&","&amp;") + "</URL>");

      xml.append("</Access>");

      return xml.toString();
   }

   private static String serialize(Dataset.Vertices vertices)
   {
      StringBuilder xml = new StringBuilder();
      xml.append("<vertices>");
      xml.append("<SkyCoordSystem>");
      for(int ix = 0; ix < vertices.VERT_COUNT; ix++)
      {
         xml.append("<P" + (ix+1) + ">");
         xml.append("<longitude>" + vertices.lon[ix] + "</longitude>");
         xml.append("<latitude>"  + vertices.lat[ix] + "</latitude>");
         xml.append("</P" + (ix+1) + ">");
      }
      xml.append("</SkyCoordSystem>");
      xml.append("</vertices>");

      return xml.toString();
   }


   private static String serialize(Dataset dataset)
   {
      StringBuilder xml = new StringBuilder();

      xml.append("<datacube>");
      xml.append(serializeOverlapCode("overlap", dataset.overlapCode));
      if(dataset.overlapCodeSky > 0)
      {
         xml.append(serializeOverlapCode("overlapSky", dataset.overlapCodeSky));
      }
      if(dataset.overlapCodeVel > 0)
      {
         xml.append(serializeOverlapCode("overlapVelocity", dataset.overlapCodeVel));
      }
      xml.append("<DataType>" + dataset.dataType + "</DataType>");
      xml.append("<PublisherDID>" + dataset.publisherDid + "</PublisherDID>");
      xml.append(serialize(dataset.access));
      xml.append(serialize(dataset.vertices_deg));
      xml.append("</datacube>");

      return xml.toString();
   }


   private static String serializeOverlapCode(String tagName, int ovCode)
   {
      final String[] overString =
      {
         "The check could not be performed because the input Region could not be mapped into the coordinate system of the datacube Region.",
         "There is no overlap between the two Regions.",
         "The datacube Region is completely inside the input Region.",
         "The input Region is completely inside the datacube Region.",
         "There is partial overlap between the two Regions.",
         "The Regions are identical to within their uncertainties.",
         "The input Region is the exact negation of the datacube Region to within their uncertainties."
      };

      StringBuilder xml = new StringBuilder();
      xml.append("<" + tagName + ">");
      xml.append("<description>" + ( ((ovCode>=0) && (ovCode<=6)) ? overString[ovCode] : (" ovCode out-of-range: "+ Integer.toString(ovCode)) ) + "</description>");
      xml.append("<code>"        + ovCode             + "</code>");
      xml.append("</" + tagName + ">");
      return xml.toString();
   }


}