/* -*- 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-2018 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.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*-------------------------------------------------------------------*
 *                             Includes                                      *
 *-------------------------------------------------------------------*/
#include <string.h>
#include <cpl.h>
#include <muse.h>
#include "muse_illum_z.h"


typedef struct muse_illumflat muse_illumflat;

struct muse_illumflat
{
    cpl_propertylist *header;
    cpl_image *image;
};

typedef void (*(*muse_allocator_func)(void));
typedef void (*muse_free_func)(void *);


/*-------------------------------------------------------------------*
 *                             Functions code                                *
 *-------------------------------------------------------------------*/

static void **
muse_valloc(cpl_size size, muse_allocator_func constructor)
{
  void **self = cpl_calloc(size, sizeof *self);

  cpl_size pos;
  for (pos = 0; pos < size; ++pos) {
    self[pos] = constructor();
  }

  return self;
}


static void
muse_vfree(void **array, cpl_size size, muse_free_func deallocator)
{
  if (array) {
    cpl_size idx;
    for (idx = 0; idx < size; ++idx) {
      if (deallocator) {
        deallocator(array[idx]);
      }
    }
    cpl_free(array);
  }
  return;
}


static muse_illumflat *
muse_illumflat_new(void)
{
  muse_illumflat *self = cpl_malloc(sizeof *self);

  self->header = NULL;
  self->image  = NULL;

  return self;
}


static void
muse_illumflat_delete(muse_illumflat *self)
{

  if (self) {
    if (self->header) {
      cpl_propertylist_delete(self->header);
    }
    if (self->image) {
      cpl_image_delete(self->image);
    }
    cpl_free(self);
  }

  return;
}


/*-------------------------------------------------------------------*/
/**
  @brief    Interpret the command line options and execute the data processing
  @param    aProcessing   the processing structure
  @param    aParams       the parameters list
  @return   0 if everything is ok, -1 something went wrong
 */
/*-------------------------------------------------------------------*/
int
muse_illum_compute(muse_processing *aProcessing, muse_illum_params_t *aParams)
{

  CPL_UNUSED(aParams);

  cpl_size nframes = cpl_frameset_get_size(aProcessing->inframes);
  cpl_ensure(nframes == 1, CPL_ERROR_ILLEGAL_INPUT, -1);


  const cpl_frame *frame = cpl_frameset_find(aProcessing->inframes,
                                             "ILLUMFLAT");
  const char *filename = cpl_frame_get_filename(frame);

  cpl_msg_info(__func__, "Loading raw illumination flat field '%s'",
               filename);

  /* Find the channel data extensions */

  cpl_size *extensions = cpl_malloc(kMuseNumIFUs * sizeof(cpl_size));

  unsigned char kifu = 0;
  unsigned char nifu;
  for (nifu = 0; nifu < kMuseNumIFUs; ++nifu) {
    char *extname = cpl_sprintf("CHAN%02hhu", nifu + 1);
    cpl_size extension = cpl_fits_find_extension(filename, extname);
    if (extension < 0) {
      cpl_msg_error(__func__, "Cannot read extension '%s' from input "
                    "file '%s'", extname, filename);
      cpl_free(extensions);
      cpl_free(extname);
      return -1;
    } else if (extension > 0) {
      extensions[kifu] = extension;
      ++kifu;
    } else {
      cpl_msg_warning(__func__, "Input file '%s' does not contain data "
                      "for IFU '%s'!", filename, extname);
    }
    cpl_free(extname);
  }

  if (kifu == 0) {
    cpl_msg_error(__func__, "Input file '%s' does not contain any IFU "
                  "data", filename);
    cpl_free(extensions);
    return -1;
  }

  /* Create container for the illumination flat field extensions and *
   * also account for the primary data unit                          */

  cpl_size nextensions = kifu + 1;

  muse_illumflat **illumflats = (muse_illumflat **)
      muse_valloc(nextensions, (muse_allocator_func)muse_illumflat_new);

  for (nifu = 0; nifu <= kifu; ++nifu) {
    muse_illumflat *illumflat = illumflats[nifu];

    cpl_size iext = (nifu == 0) ? 0 : extensions[nifu - 1];

    illumflat->header = cpl_propertylist_load(filename, iext);
    if (!illumflat->header) {
      cpl_msg_error(__func__, "Cannot not read metadata of extension '%"
                    CPL_SIZE_FORMAT "' of '%s'!", iext, filename);
      muse_vfree((void **)illumflats, nextensions,
                 (muse_free_func)muse_illumflat_delete);
      cpl_free(extensions);

      return -1;
    }

    illumflat->image = NULL;
    if (nifu > 0) {
      illumflat->image = cpl_image_load(filename, CPL_TYPE_INT, 0, iext);
      if (!illumflat->image) {
        cpl_msg_error(__func__, "Cannot not read image data of extension '%"
                      CPL_SIZE_FORMAT "' of '%s'!", iext, filename);
        muse_vfree((void **)illumflats, nextensions,
                   (muse_free_func)muse_illumflat_delete);
        cpl_free(extensions);

        return -1;
      }
    }
  }

  cpl_free(extensions);

  muse_processing_append_used(aProcessing, (cpl_frame *)frame,
                                    CPL_FRAME_GROUP_RAW, 1);

  cpl_msg_info(__func__, " Converting raw ilumination flat-field to product");

  /* Create a DFS compliant product header for the unprocessed *
   * illumination flat field                                   */

  cpl_propertylist *header = cpl_propertylist_duplicate(illumflats[0]->header);
  cpl_frame *product_frame =
      muse_processing_new_frame(aProcessing, -1, header, "ILLUM",
                                CPL_FRAME_TYPE_IMAGE);
  if (!product_frame) {
    cpl_msg_error(__func__, "Could not create product frame for input file "
                  "'%s'", filename);
    cpl_propertylist_delete(header);

    muse_vfree((void **)illumflats, nextensions,
               (muse_free_func)muse_illumflat_delete);
    return -1;
  }

  /* Save the product illumination flat field */

  cpl_propertylist_save(header, cpl_frame_get_filename(product_frame),
                        CPL_IO_CREATE);

  cpl_size iextension;
  for (iextension = 1; iextension < nextensions; ++iextension) {
    muse_illumflat *illumflat = illumflats[iextension];

    cpl_errorstate status = cpl_errorstate_get();

    cpl_image_save(illumflat->image, cpl_frame_get_filename(product_frame),
                   CPL_TYPE_INT, illumflat->header, CPL_IO_EXTEND);

    if (!cpl_errorstate_is_equal(status)) {
      const char *extname = muse_pfits_get_extname(illumflat->header);
      if (!extname) {
        extname = "UNKNOWN";
      }
      cpl_msg_error(__func__, "Cannot save extension '%s' to recipe product "
                    "file %s", extname, cpl_frame_get_filename(product_frame));

      cpl_propertylist_delete(header);

      muse_vfree((void **)illumflats, nextensions,
                 (muse_free_func)muse_illumflat_delete);
      return -1;
    }
  }

  cpl_propertylist_delete(header);

  muse_vfree((void **)illumflats, nextensions,
             (muse_free_func)muse_illumflat_delete);


  cpl_frameset_insert(aProcessing->outframes, product_frame);

  return 0;
} /* muse_illum_compute() */
