/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set sw=2 sts=2 et cin: */
/*
 * This file is part of the MUSE Instrument Pipeline
 * Copyright (C) 2005-2014 European Southern Observatory
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
#include <math.h>
#include <dlfcn.h>
#include <string.h>
#include <cpl.h>
#include <muse.h>

static const char *
muse_xmldoc_get_typestring(cpl_type type) {
  return
    ((type & ~CPL_TYPE_POINTER) == CPL_TYPE_BOOL)?"boolean":
    ((type & ~CPL_TYPE_POINTER) == CPL_TYPE_INT)?"int":
    ((type & ~CPL_TYPE_POINTER) == CPL_TYPE_DOUBLE)?"double":
    ((type & ~CPL_TYPE_POINTER) == CPL_TYPE_FLOAT)?"float":
    ((type & ~CPL_TYPE_POINTER) == CPL_TYPE_STRING)?"string":
    "unknown";
}

static void print_escaped(const char *s) {
  while (*s != '\0') {
    switch(*s) {
        case '<': printf("&lt;"); break;
        case '&': printf("&amp;"); break;
        default: putchar(*s); break;
    }
    s++;
  }
}

static void
muse_xmldoc_dump_parameter(cpl_parameter *param, const char *context) {
  cpl_type type = cpl_parameter_get_type(param);
  cpl_parameter_class class = cpl_parameter_get_class(param);

  printf("      <parameter");
  if (cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_CLI) != NULL) {
    printf(" name=\"%s\"", cpl_parameter_get_alias(param,
                                                   CPL_PARAMETER_MODE_CLI));
  } else if (cpl_parameter_get_alias(param,
                                     CPL_PARAMETER_MODE_CFG) != NULL) {
    printf(" name=\"%s\"",
           cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_CFG));
  } else if (cpl_parameter_get_alias(param,
                                     CPL_PARAMETER_MODE_ENV) != NULL) {
    printf(" name=\"%s\"",
           cpl_parameter_get_alias(param, CPL_PARAMETER_MODE_ENV));
  } else {
    printf(" name=\"%s\"", cpl_parameter_get_name(param));
  }
  if (strcmp(cpl_parameter_get_context(param), context) != 0) {
    printf(" context=\"%s\"", cpl_parameter_get_context(param));
  }
  printf(" type=\"%s\">\n", muse_xmldoc_get_typestring(type));
  printf("        <description>");
  print_escaped(cpl_parameter_get_help(param));
  printf("</description>\n");
  if (class == CPL_PARAMETER_CLASS_RANGE) {
    switch (type) {
    case CPL_TYPE_INT:
      printf("        <range min=\"%d\" max=\"%d\"/>\n",
             cpl_parameter_get_range_min_int(param),
             cpl_parameter_get_range_max_int(param));
      break;
    case CPL_TYPE_DOUBLE:
      printf("        <range min=\"%g\" max=\"%g\"/>\n",
             cpl_parameter_get_range_min_double(param),
             cpl_parameter_get_range_max_double(param));
      break;
    default:
      break;
    }

  }
  if (class == CPL_PARAMETER_CLASS_ENUM) {
    int n_enum = cpl_parameter_get_enum_size(param);
    printf("        <enumeration>\n");
    int i;
    for (i = 0; i < n_enum; i++) {
      switch (type) {
      case CPL_TYPE_INT:
        printf("          <value>%i</value>\n",
               cpl_parameter_get_enum_int(param, i));
        break;
      case CPL_TYPE_DOUBLE:
        printf("          <value>%g</value>\n",
               cpl_parameter_get_enum_double(param, i));
        break;
      case CPL_TYPE_STRING:
        printf("          <value>\"");
        print_escaped(cpl_parameter_get_enum_string(param, i));
        printf("\"</value>\n");
        break;
      default:
        break;
      }
    }
    printf("        </enumeration>\n");
  }

  switch (type) {
  case CPL_TYPE_BOOL:
    printf("        <default>%s</default>\n",
           (cpl_parameter_get_default_bool(param))?"true":"false");
    break;
  case CPL_TYPE_INT:
    printf("        <default>%i</default>\n",
           cpl_parameter_get_default_int(param));
    break;
  case CPL_TYPE_DOUBLE:
    printf("        <default>%g</default>\n",
           cpl_parameter_get_default_double(param));
      break;
  case CPL_TYPE_STRING:
    printf("        <default>%s</default>\n",
           cpl_parameter_get_default_string(param));
      break;
  default:
    break;
  }

  printf("      </parameter>\n");
}

static void
muse_xmldoc_dump_parameters(const char *instrument, const char *recipename,
                            cpl_parameterlist *parameters) {
  if (cpl_parameterlist_get_size(parameters) > 0) {
    cpl_parameter *param = cpl_parameterlist_get_first(parameters);
    const char *context = cpl_parameter_get_context(param);
    printf("    <parameters");
    char *default_context = cpl_sprintf("%s.%s", instrument, recipename);
    if (strcmp(context, default_context) != 0) {
      printf("context=\"%s\"", context);
    }
    cpl_free(default_context);
    printf(">\n");
    for (; param != NULL;  param = cpl_parameterlist_get_next(parameters)) {
      muse_xmldoc_dump_parameter(param, context);
    }
    printf("    </parameters>\n");
  }
}

static void
muse_xmldoc_dump_fitsheader(cpl_propertylist *aHeader) {
  if (aHeader == NULL) {
    return;
  }
  cpl_propertylist_erase_regexp(aHeader, "MUSE PRIVATE.*", 0);
  int size = cpl_propertylist_get_size(aHeader);
  if (size > 0) {
    printf("          <fitsheader>\n");
    int i;
    for (i = 0; i < size; i++) {
      cpl_property *property = cpl_propertylist_get(aHeader, i);
      printf("            <property type=\"%s\">\n",
             muse_xmldoc_get_typestring(cpl_property_get_type(property)));
      printf("              <name>");
      print_escaped(cpl_property_get_name(property));
      printf("</name>\n");
      if (cpl_property_get_comment(property) != NULL) {
        printf("              <description>");
        print_escaped(cpl_property_get_comment(property));
        printf("</description>\n");
      }
      if ((cpl_property_get_type(property) == CPL_TYPE_DOUBLE) &&
          !isnan(cpl_property_get_double(property))) {
        printf("              <value>%g</value>\n",
               cpl_property_get_double(property));
      }
      if ((cpl_property_get_type(property) == CPL_TYPE_FLOAT) &&
          !isnan(cpl_property_get_double(property))) {
        printf("              <value>%g</value>\n",
               cpl_property_get_float(property));
      }
      if ((cpl_property_get_type(property) == CPL_TYPE_LONG) &&
          (cpl_property_get_long(property) != LONG_MAX)) {
        printf("              <value>%li</value>\n",
               cpl_property_get_long(property));
      }
      if ((cpl_property_get_type(property) == CPL_TYPE_INT) &&
          (cpl_property_get_int(property) != INT_MAX)) {
        printf("              <value>%i</value>\n",
               cpl_property_get_int(property));
      }
      if ((cpl_property_get_type(property) == CPL_TYPE_STRING) &&
          (cpl_property_get_string(property) != NULL)) {
        printf("              <value>");
        print_escaped(cpl_property_get_string(property));
        printf("</value>\n");
      }

      printf("            </property>\n");
    }
    printf("         </fitsheader>\n");
  }
}


static void
muse_xmldoc_dump_frames(cpl_recipe *recipe) {
  cpl_recipeconfig *recipeconfig = muse_processing_get_recipeconfig(recipe);
  if (recipeconfig == NULL) {
    return;
  }
  printf("    <frames>\n");
  char **tags = cpl_recipeconfig_get_tags(recipeconfig);
  int i_tag;
  for (i_tag = 0; tags[i_tag] != NULL; i_tag++) {
    printf("      <frameset>\n");
    {
      int min = cpl_recipeconfig_get_min_count(recipeconfig,
                                               tags[i_tag], tags[i_tag]);
      int max = cpl_recipeconfig_get_max_count(recipeconfig,
                                               tags[i_tag], tags[i_tag]);
      printf("        <frame tag=\"%s\" group=\"raw\"", tags[i_tag]);
      if (min > 0) {
        printf(" min=\"%i\"", min);
      }
      if (max > 0) {
        printf(" max=\"%i\"", max);
      }
      printf("/>\n");
    }

    char **inputs = cpl_recipeconfig_get_inputs(recipeconfig, tags[i_tag]);
    int i_input;
    for (i_input = 0; inputs[i_input] != NULL; i_input++) {
      int min = cpl_recipeconfig_get_min_count(recipeconfig,
                                               tags[i_tag], inputs[i_input]);
      int max = cpl_recipeconfig_get_max_count(recipeconfig,
                                               tags[i_tag], inputs[i_input]);
      printf("        <frame tag=\"%s\" group=\"calib\"", inputs[i_input]);
      if (min > 0) {
        printf(" min=\"%i\"", min);
      }
      if (max > 0) {
        printf(" max=\"%i\"", max);
      }
      printf("/>\n");
      cpl_free(inputs[i_input]);
    }
    cpl_free(inputs);

    char **outputs = cpl_recipeconfig_get_outputs(recipeconfig, tags[i_tag]);
    int i_output;
    for (i_output = 0; outputs[i_output] != NULL; i_output++) {
      printf("        <frame tag=\"%s\" group=\"product\" base=\"%s\"",
             outputs[i_output], tags[i_tag]);
      cpl_frame_level l = muse_processing_get_frame_level(recipe,
                                                          outputs[i_output]);
      switch (l) {
      case CPL_FRAME_LEVEL_TEMPORARY:
        printf(" level=\"temporary\"");
        break;
      case CPL_FRAME_LEVEL_INTERMEDIATE:
        printf(" level=\"intermediate\"");
        break;
      case CPL_FRAME_LEVEL_FINAL:
        printf(" level=\"final\"");
        break;
      default:
        break;
      }
      int m = muse_processing_get_frame_mode(recipe, outputs[i_output]);
      switch (m) {
      case MUSE_FRAME_MODE_MASTER:
        printf(" mode=\"master\"");
        break;
      case MUSE_FRAME_MODE_DATEOBS:
        printf(" mode=\"dateobs\"");
        break;
      case MUSE_FRAME_MODE_SUBSET:
        printf(" mode=\"subset\"");
        break;
      default: /* MUSE_FRAME_MODE_ALL */
        printf(" mode=\"all\"");
      }
      printf(">\n");
      cpl_propertylist *header = cpl_propertylist_new();
      cpl_propertylist_append_string(header,
                                     "MUSE PRIVATE DOCUMENTATION", "xml");
      muse_processing_prepare_header(recipe, outputs[i_output], header);
      cpl_propertylist_erase(header, "MUSE PRIVATE DOCUMENTATION");
      muse_xmldoc_dump_fitsheader(header);
      cpl_propertylist_delete(header);
      cpl_free(outputs[i_output]);
      printf("        </frame>\n");
    }
    cpl_free(outputs);
    cpl_free(tags[i_tag]);
    printf("      </frameset>\n");
  }
  cpl_free(tags);
  printf("    </frames>\n");
}

static void
muse_xmldoc_dump_plugin(const char *instrument,
                        const char *libname, cpl_plugin *plugin) {
  if (strrchr(libname, '/') != NULL) {
    libname = strrchr(libname, '/')+1;
  }

  printf("  <plugin name=\"%s\" type=\"%s\">\n",
         cpl_plugin_get_name(plugin),
         cpl_plugin_get_type_string(plugin));
  printf("    <plugininfo>\n");
  printf("      <synopsis>");
  print_escaped(cpl_plugin_get_synopsis(plugin));
  printf("</synopsis>\n");

  printf("      <description>");
  print_escaped(cpl_plugin_get_description(plugin));
  printf("</description>\n");
         
  printf("      <author>");
  print_escaped(cpl_plugin_get_author(plugin));
  printf("</author>\n");
  printf("      <email>");
  print_escaped(cpl_plugin_get_email(plugin));
  printf("</email>\n");
  printf("      <version>");
  print_escaped(cpl_plugin_get_version_string(plugin));
  printf("</version>\n");
  printf("      <library>");
  print_escaped(libname);
  printf("</library>\n");
  printf("    </plugininfo>\n");

  if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) {
    cpl_recipe *recipe = (cpl_recipe *)plugin;
    if (recipe->parameters) {
      muse_xmldoc_dump_parameters(instrument, cpl_plugin_get_name(plugin),
                                  recipe->parameters);
    }
    muse_xmldoc_dump_frames(recipe);
  }
  printf("  </plugin>\n");

}

// The pedantic warning 
// "ISO C forbids initialization between function pointer and 'void *'"
// is a known and unsolved flaw of dlsym.
// see http://pubs.opengroup.org/onlinepubs/009695399/functions/dlsym.html#tag_03_112_08
#pragma GCC diagnostic ignored "-Wpedantic"
static void
muse_xmldoc_dump_library(const char *instrument, const char *libname) {
  void *handle = dlopen(libname, RTLD_LAZY);
  if (handle == NULL) {
    cpl_msg_warning(__func__, "could not open %s", libname);
    return;
  }

  dlerror();
  int (*cpl_plugin_get_info_d)(cpl_pluginlist *) = dlsym(handle,
                                                         "cpl_plugin_get_info");

  char *error = dlerror();
  if (error != NULL)  {
    cpl_msg_warning(__func__, "%s", error);
    return;
  }

  cpl_pluginlist *list = cpl_pluginlist_new();
  (*cpl_plugin_get_info_d)(list);
  cpl_plugin *plugin;
  for (plugin = cpl_pluginlist_get_first(list);
       plugin != NULL;
       plugin = cpl_pluginlist_get_next(list)) {
    cpl_plugin_get_init(plugin)(plugin);
    muse_xmldoc_dump_plugin(instrument, libname, plugin);
    cpl_plugin_get_deinit(plugin)(plugin);
    if (cpl_plugin_get_type(plugin) == CPL_PLUGIN_TYPE_RECIPE) {
      muse_processinginfo_delete((cpl_recipe *)plugin);
    }
  }
  cpl_pluginlist_delete(list);
  dlclose(handle);
  return;
}

static int
muse_xmldoc_dump_table(const char *libname, const char *table) {
  void *handle = dlopen(libname, RTLD_LAZY);
  if (handle == NULL) {
    return 0;
  }

  dlerror();
  muse_cpltable_def *tabledef = dlsym(handle, table);
  char *error = dlerror();
  if (error != NULL || tabledef == NULL)  {
    return 0;
  }

  printf("    <tabledef name=\"%s\">\n", table);
  for (; tabledef->name != NULL; tabledef++) {
    printf("      <column type=\"%s\"",
           muse_xmldoc_get_typestring(tabledef->type));
    if (tabledef->type & CPL_TYPE_POINTER) {
      printf(" array=\"true\"");
    }
    printf(">\n");
    printf("        <name>%s</name>\n", tabledef->name);
    if (tabledef->unit != NULL && strlen(tabledef->unit) > 0) {
      printf("        <unit>%s</unit>\n", tabledef->unit);
    }
    if (tabledef->format != NULL && strlen(tabledef->format) > 0) {
      printf("        <format>%s</format>\n", tabledef->format);
    }
    if (tabledef->description != NULL) {
      printf("        <description>");
      print_escaped(tabledef->description);
      printf("</description>\n");
    }
    printf("      </column>\n");
  }
  printf("    </tabledef>\n");
  dlclose(handle);
  return 1;
}

/*--------------------------------------------------------------------------*/
/**
   @brief Main function for use as independent program
*/
/*--------------------------------------------------------------------------*/
int main(int argc, char **argv) {
  cpl_init(CPL_INIT_DEFAULT);
  cpl_error_reset();
  cpl_msg_set_component_on();
  cpl_msg_set_level(CPL_MSG_DEBUG);
  const char *instrument = "muse";

  printf("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
         "<pluginlist instrument=\"%s\" cpl_version=\"%s\">\n",
         instrument,
         cpl_version_get_version());
  {
    int i;
    for (i = 1; i < argc; i++) {
      const char *libname = argv[i];
      muse_xmldoc_dump_library(instrument, libname);
    }
  }

  const char *tables[] = {
    "muse_sky_lines_def",
    "muse_ohtransition_def",
    "muse_fluxspectrum_def",
    "muse_relativespectrum_def",
    "muse_extinction_def",
    "muse_sky_dataspectrum_def",
    "muse_detectorparams_def",
    "muse_pixtable_def",
    NULL
  };

  printf("  <definitions>\n");
  const char **t;
  for (t = tables; *t != NULL; t++) {
    int i;
    for (i = 1; i < argc; i++) {
      const char *libname = argv[i];
      int r = muse_xmldoc_dump_table(libname, *t);
      if (r) break;
    }
  }
  printf("  </definitions>\n");

  printf("</pluginlist>\n");
  exit(0);
}

