/* -*- 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) 2015-16 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.
 */

#define _BSD_SOURCE /* get mkdtemp() from stdlib.h */
#include <stdlib.h>
#include <unistd.h> /* for chdir() */
#include <sys/stat.h> /* for mkdir() */
#include <string.h>

#include <muse.h>

#define DIR_TEMPLATE "/tmp/muse_utils_test_XXXXXX"

typedef struct {
  const char *fn;
  const char *tag;
  cpl_frame_group group;
} musefset;

static cpl_frameset *
muse_test_utils_new_frameset(musefset aSet[])
{
  cpl_frameset *frames = cpl_frameset_new();
  int i = 0, n = 0;
  while (aSet[i].fn) {
    cpl_frame *f = cpl_frame_new();
    cpl_frame_set_filename(f, aSet[i].fn);
    cpl_frame_set_tag(f, aSet[i].tag);
    cpl_frame_set_group(f, aSet[i].group);
    cpl_frameset_insert(frames, f);
    i++, n++;
  } /* while */
#if 0
  cpl_msg_debug(__func__, "%d frames", n);
#endif
  return frames;
} /* muse_test_utils_new_frameset() */

static char *
muse_test_utils_create_files(musefset aSet[])
{
  char dirtemplate[FILENAME_MAX] = DIR_TEMPLATE;
  char *dir = mkdtemp(dirtemplate);
  cpl_test_zero(chdir(dir));
  mkdir("RAW", S_IRWXU); /* only to be used by the creator */
  cpl_msg_debug(__func__, "Created dir \"%s/%s\"...", dir, "RAW");

  int i = 0;
  while (aSet[i].fn) {
    if (strstr(aSet[i].fn, "2016")) {
      /* get the 23chars starting with 2016 from the filename */
      char *dateobs = cpl_sprintf("%.23s", strstr(aSet[i].fn, "2016"));
      cpl_msg_debug(__func__, "%d %s", i, dateobs);
      cpl_propertylist *p = cpl_propertylist_new();
      cpl_propertylist_append_string(p, "FILENAME", aSet[i].fn);
      cpl_propertylist_append_string(p, "DATE-OBS", dateobs);
      cpl_free(dateobs);
      cpl_propertylist_save(p, aSet[i].fn, CPL_IO_CREATE);
      cpl_propertylist_delete(p);
    } /* filename with DATE-OBS */
    i++;
  } /* while */
  return cpl_strdup(dir);
} /* muse_test_utils_create_files() */

static cpl_error_code
muse_test_utils_delete_files(musefset aSet[], char *aDir)
{
  int i = 0;
  while (aSet[i].fn) {
    if (strstr(aSet[i].fn, "2016")) {
      remove(aSet[i].fn);
    } /* filename with DATE-OBS */
    i++;
  } /* while */
  cpl_test_zero(rmdir("RAW"));
  cpl_test_zero(rmdir(aDir));
  cpl_msg_debug(__func__, "... removed dir \"RAW\" and \"%s\".", aDir);
  return CPL_ERROR_NONE;
} /* muse_test_utils_delete_files() */

static cpl_error_code
muse_test_utils_compare_framesets(const cpl_frameset *aF1,
                                  const cpl_frameset *aF2)
{
  int i, nframes = cpl_frameset_get_size(aF1);
  if (cpl_frameset_get_size(aF2) != nframes) {
    cpl_msg_debug(__func__, "Inequal number of frames: %d %d", nframes,
                  (int)cpl_frameset_get_size(aF2));
    return CPL_ERROR_ILLEGAL_INPUT;
  }
#if 0
  cpl_frameset_dump(aF1, stdout);
  cpl_frameset_dump(aF2, stdout);
  fflush(stdout);
#endif
  for (i = 0; i < nframes; i++) {
    const cpl_frame *f1 = cpl_frameset_get_position_const(aF1, i),
                    *f2 = cpl_frameset_get_position_const(aF2, i);
    if (strcmp(cpl_frame_get_filename(f1), cpl_frame_get_filename(f2))) {
      cpl_msg_debug(__func__, "bad fn: %s %s", cpl_frame_get_filename(f1),
                    cpl_frame_get_filename(f2));
      return CPL_ERROR_INCOMPATIBLE_INPUT;
    }
    if (strcmp(cpl_frame_get_tag(f1), cpl_frame_get_tag(f2))) {
      cpl_msg_debug(__func__, "bad tag: %s %s", cpl_frame_get_tag(f1),
                    cpl_frame_get_tag(f2));
      return CPL_ERROR_INCOMPATIBLE_INPUT;
    }
#if 0
    cpl_msg_debug(__func__, "OK: %s, %s", cpl_frame_get_filename(f1),
                    cpl_frame_get_tag(f1));
#endif
  } /* for i (all frames) */
  return CPL_ERROR_NONE;
} /* muse_test_utils_compare_framesets() */

/*----------------------------------------------------------------------------*/
/**
  @brief    Test program to check that functions from muse_utils work when
            called with the necessary data.

  This program explicitely tests
    muse_utils_frameset_merge_frames()   (but only for trivial cases!)
 */
/*----------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
  UNUSED_ARGUMENTS(argc, argv);
  cpl_test_init(PACKAGE_BUGREPORT, CPL_MSG_DEBUG);

  /* Test trivial cases of muse_utils_frameset_merge_frames() */
  cpl_errorstate ps = cpl_errorstate_get();
  cpl_error_code rc = muse_utils_frameset_merge_frames(NULL, CPL_TRUE);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_NULL_INPUT);
  cpl_errorstate_set(ps);
  /* check with existing but empty frameset */
  cpl_frameset *frameset = cpl_frameset_new();
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(!cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_ILLEGAL_INPUT);
  cpl_errorstate_set(ps);
  /* since testing on real files is too costly in development, *
   * make up a minimal frameset, first with one, then with two *
   * frames for very simple testing                            */
  cpl_frame *frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "WAVECAL_TABLE-10.fits");
  cpl_frame_set_tag(frame, MUSE_TAG_WAVECAL_TABLE);
  cpl_frameset_insert(frameset, frame);
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "WAVECAL_TABLE-10.fits")); /* filename unchanged */
  /* now with 2nd frame */
  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "WAVECAL_TABLE-11.fits");
  cpl_frame_set_tag(frame, MUSE_TAG_WAVECAL_TABLE);
  cpl_frameset_insert(frameset, frame);
  int errstate = errno;
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test_noneq(errstate, errno); /* the C error state has changed! */
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "WAVECAL_TABLE-10.fits")); /* filename unchanged */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 1)),
                       "WAVECAL_TABLE-11.fits")); /* filename unchanged */
  cpl_frameset_dump(frameset, stdout);
  fflush(stdout);
  cpl_frameset_delete(frameset);

  /* now test the same with pixel table frames */
  frameset = cpl_frameset_new();
  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "PIXTABLE_OBJECT-10.fits");
  cpl_frame_set_tag(frame, "PIXTABLE_OBJECT");
  cpl_frameset_insert(frameset, frame);
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "PIXTABLE_OBJECT-10.fits")); /* filename unchanged */

  frame = cpl_frame_new();
  cpl_frame_set_filename(frame, "PIXTABLE_OBJECT-11.fits");
  cpl_frame_set_tag(frame, "PIXTABLE_OBJECT");
  cpl_frameset_insert(frameset, frame);
  errstate = errno;
  rc = muse_utils_frameset_merge_frames(frameset, CPL_TRUE);
  cpl_test_eq(errstate, errno); /* no change this time, since merging not done */
  cpl_test(rc == CPL_ERROR_NONE); /* nothing done (WARNING output) but OK */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 0)),
                       "PIXTABLE_OBJECT-10.fits")); /* filename unchanged */
  cpl_test_zero(strcmp(cpl_frame_get_filename(cpl_frameset_get_position(frameset, 1)),
                       "PIXTABLE_OBJECT-11.fits")); /* filename unchanged */
  cpl_frameset_dump(frameset, stdout);
  fflush(stdout);
  cpl_frameset_delete(frameset);

  /* test muse_frameset_sort_raw_other() with a few semi-real framesets */
  musefset fset1[] = { { "RAW/MUSE.2016-02-01T23:24:58.069.fits.fz", "ILLUM",           CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:34:54.532.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:36:14.186.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-01T23:37:34.014.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_RAW },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",     CPL_FRAME_GROUP_CALIB },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",   CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/vignetting_mask.fits",          "VIGNETTING_MASK", CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",    CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE",  CPL_FRAME_GROUP_CALIB },
                       { NULL,                                       NULL,              CPL_FRAME_GROUP_NONE }
                     },
           fset2[] = { { "RAW/MUSE.2016-02-01T23:34:54.532.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:36:14.186.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:37:34.014.fits.fz", "SKYFLAT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-01T23:24:58.069.fits.fz", "ILLUM",           CPL_FRAME_GROUP_NONE },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",    CPL_FRAME_GROUP_NONE },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE",  CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",     CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",     CPL_FRAME_GROUP_NONE },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",     CPL_FRAME_GROUP_NONE },
                       { "muse-calib/vignetting_mask.fits",          "VIGNETTING_MASK", CPL_FRAME_GROUP_NONE },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",   CPL_FRAME_GROUP_NONE },
                       { NULL,                                       NULL,              CPL_FRAME_GROUP_NONE }
                     };
  cpl_frameset *fin = muse_test_utils_new_frameset(fset1),
               *fout = muse_test_utils_new_frameset(fset2),
               *fsorted = muse_frameset_sort_raw_other(fin, 0, NULL, CPL_TRUE);
  cpl_frameset_delete(fin);
  cpl_test_eq(muse_test_utils_compare_framesets(fout, fsorted), CPL_ERROR_NONE);
  cpl_frameset_delete(fout);
  cpl_frameset_delete(fsorted);

  /* for the following we need a temporary directory */
  musefset fset3[] = { { "RAW/MUSE.2016-02-02T06:11:47.516.fits.fz", "ILLUM",          CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T08:42:35.875.fits.fz", "ILLUM",          CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T09:08:12.890.fits.fz", "STD",            CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T06:16:26.342.fits.fz", "OBJECT",         CPL_FRAME_GROUP_RAW },
                       { "RAW/MUSE.2016-02-02T06:40:32.217.fits.fz", "OBJECT",         CPL_FRAME_GROUP_RAW },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",   CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",    CPL_FRAME_GROUP_CALIB },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",  CPL_FRAME_GROUP_CALIB },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE", CPL_FRAME_GROUP_CALIB },
                       { "CALIB/TWILIGHT_CUBE_02e.fits",             "TWILIGHT_CUBE",  CPL_FRAME_GROUP_CALIB },
                       { NULL,                                       NULL,             CPL_FRAME_GROUP_NONE }
                     },
           fset4[] = { { "RAW/MUSE.2016-02-02T06:40:32.217.fits.fz", "OBJECT",         CPL_FRAME_GROUP_NONE },
                       { "RAW/MUSE.2016-02-02T06:11:47.516.fits.fz", "ILLUM",          CPL_FRAME_GROUP_NONE },
                       { "muse-calib/badpix_table.fits",             "BADPIX_TABLE",   CPL_FRAME_GROUP_NONE },
                       { "muse-calib/geometry_table_wfm_gto11.fits", "GEOMETRY_TABLE", CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_BIAS_02.fits",                "MASTER_BIAS",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/MASTER_FLAT_02e.fits",               "MASTER_FLAT",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/TRACE_TABLE_02e.fits",               "TRACE_TABLE",    CPL_FRAME_GROUP_NONE },
                       { "CALIB/TWILIGHT_CUBE_02e.fits",             "TWILIGHT_CUBE",  CPL_FRAME_GROUP_NONE },
                       { "CALIB/WAVECAL_TABLE_02e.fits",             "WAVECAL_TABLE",  CPL_FRAME_GROUP_NONE },
                       { NULL,                                       NULL,             CPL_FRAME_GROUP_NONE }
                     };
  char *dir = muse_test_utils_create_files(fset3);
  fin = muse_test_utils_new_frameset(fset3);
  fout = muse_test_utils_new_frameset(fset4);
  ps = cpl_errorstate_get();
  fsorted = muse_frameset_sort_raw_other(fin, 2, "2016-02-02T06:40:32.217", CPL_FALSE);
  cpl_test_eq(muse_test_utils_compare_framesets(fout, fsorted), CPL_ERROR_NONE);
  cpl_test(cpl_errorstate_is_equal(ps));
  cpl_frameset_delete(fsorted);
  /* failure case: wrongly assumed exposure sequence */
  fsorted = muse_frameset_sort_raw_other(fin, 2, "2016-02-02T06:40:32.217", CPL_TRUE);
  ps = cpl_errorstate_get();
  rc = muse_test_utils_compare_framesets(fout, fsorted);
  cpl_test(cpl_errorstate_is_equal(ps) && rc == CPL_ERROR_ILLEGAL_INPUT); /* wrong number found */
  cpl_frameset_delete(fin);
  cpl_frameset_delete(fout);
  cpl_frameset_delete(fsorted);
  muse_test_utils_delete_files(fset3, dir);
  cpl_free(dir);

  return cpl_test_end(0);
}
