/*
 * #%L
 * OME-FILES C++ library for image IO.
 * Copyright © 2015 Open Microscopy Environment:
 *   - Massachusetts Institute of Technology
 *   - National Institutes of Health
 *   - University of Dundee
 *   - Board of Regents of the University of Wisconsin-Madison
 *   - Glencoe Software, Inc.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of any organization.
 * #L%
 */

#include <iostream>

#include <boost/filesystem/path.hpp>

#include <ome/files/CoreMetadata.h>
#include <ome/files/MetadataTools.h>
#include <ome/files/VariantPixelBuffer.h>
#include <ome/files/out/OMETIFFWriter.h>
#include <ome/xml/meta/OMEXMLMetadata.h>

#include <ome/compat/memory.h>

using boost::filesystem::path;
using ome::compat::make_shared;
using ome::compat::shared_ptr;
using ome::files::dimension_size_type;
using ome::files::fillMetadata;
using ome::files::CoreMetadata;
using ome::files::DIM_SPATIAL_X;
using ome::files::DIM_SPATIAL_Y;
using ome::files::DIM_SUBCHANNEL;
using ome::files::FormatWriter;
using ome::files::MetadataMap;
using ome::files::out::OMETIFFWriter;
using ome::files::PixelBuffer;
using ome::files::PixelBufferBase;
using ome::files::PixelProperties;
using ome::files::VariantPixelBuffer;
using ome::xml::model::enums::PixelType;
using ome::xml::model::enums::DimensionOrder;

namespace
{

  /* write-example-start */
  shared_ptr< ::ome::xml::meta::OMEXMLMetadata>
  createMetadata()
  {
    // OME-XML metadata store.
    shared_ptr< ::ome::xml::meta::OMEXMLMetadata> meta(make_shared< ::ome::xml::meta::OMEXMLMetadata>());

    // Create simple CoreMetadata and use this to set up the OME-XML
    // metadata.  This is purely for convenience in this example; a
    // real writer would typically set up the OME-XML metadata from an
    // existing MetadataRetrieve instance or by hand.
    std::vector<shared_ptr<CoreMetadata> > seriesList;
    shared_ptr<CoreMetadata> core(make_shared<CoreMetadata>());
    core->sizeX = 512U;
    core->sizeY = 512U;
    core->sizeC.clear(); // defaults to 1 channel with 1 subchannel; clear this
    core->sizeC.push_back(3U); // replace with single RGB channel
    core->pixelType = ome::xml::model::enums::PixelType::UINT16;
    core->interleaved = false;
    core->bitsPerPixel = 12U;
    core->dimensionOrder = DimensionOrder::XYZTC;
    seriesList.push_back(core);
    seriesList.push_back(core); // add two identical series

    fillMetadata(*meta, seriesList);

    return meta;
  }
  /* write-example-end */

  /* pixel-example-start */
  void
  writePixelData(FormatWriter& writer,
                 std::ostream& stream)
  {
    // Total number of images (series)
    dimension_size_type ic = writer.getMetadataRetrieve()->getImageCount();
    stream << "Image count: " << ic << '\n';

    // Loop over images
    for (dimension_size_type i = 0 ; i < ic; ++i)
      {
        // Change the current series to this index
        writer.setSeries(i);

        // Total number of planes.
        dimension_size_type pc = 1U;
        pc *= writer.getMetadataRetrieve()->getPixelsSizeZ(i);
        pc *= writer.getMetadataRetrieve()->getPixelsSizeT(i);
        pc *= writer.getMetadataRetrieve()->getChannelCount(i);
        stream << "\tPlane count: " << pc << '\n';

        // Loop over planes (for this image index)
        for (dimension_size_type p = 0 ; p < pc; ++p)
          {
            // Pixel buffer; size 512 × 512 with 3 subchannels of type
            // uint16_t.  It uses the native endianness and has a
            // storage order of XYZTC without interleaving
            // (subchannels are planar).
            shared_ptr<PixelBuffer<PixelProperties<PixelType::UINT16>::std_type> >
              buffer(make_shared<PixelBuffer<PixelProperties<PixelType::UINT16>::std_type> >
                     (boost::extents[512][512][1][1][1][3][1][1][1],
                      PixelType::UINT16, ome::files::ENDIAN_NATIVE,
                      PixelBufferBase::make_storage_order(DimensionOrder::XYZTC, false)));

            // Fill each subchannel with a different intensity ramp in
            // the 12-bit range.  In a real program, the pixel data
            // would typically be obtained from data acquisition or
            // another image.
            for (dimension_size_type x = 0; x < 512; ++x)
              for (dimension_size_type y = 0; y < 512; ++y)
                {
                  PixelBufferBase::indices_type idx;
                  std::fill(idx.begin(), idx.end(), 0);
                  idx[DIM_SPATIAL_X] = x;
                  idx[DIM_SPATIAL_Y] = y;
                  
                  idx[DIM_SUBCHANNEL] = 0;
                  buffer->at(idx) = (static_cast<float>(x) / 512.0f) * 4096.0f;
                  idx[DIM_SUBCHANNEL] = 1;
                  buffer->at(idx) = (static_cast<float>(y) / 512.0f) * 4096.0f;
                  idx[DIM_SUBCHANNEL] = 2;
                  buffer->at(idx) = (static_cast<float>(x+y) / 1024.0f) * 4096.0f;
                }

            VariantPixelBuffer vbuffer(buffer);
            stream << "PixelBuffer PixelType is " << buffer->pixelType() << '\n';
            stream << "VariantPixelBuffer PixelType is " << vbuffer.pixelType() << '\n';
            stream << std::flush;

            // Write the the entire pixel buffer to the plane.
            writer.saveBytes(p, vbuffer);

            stream << "Wrote " << buffer->num_elements() << ' ' << buffer->pixelType() << " pixels\n";
          }
      }
  }
  /* pixel-example-end */

}

int
main(int argc, char *argv[])
{
  // This is the default, but needs setting manually on Windows.
  ome::common::setLogLevel(ome::logging::trivial::warning);

  try
    {
      if (argc > 1)
        {
          // Portable path
          path filename(argv[1]);

          /* writer-example-start */
          // Create metadata for the file to be written.
          shared_ptr< ::ome::xml::meta::MetadataRetrieve> meta(createMetadata());

          // Create TIFF writer
          shared_ptr<FormatWriter> writer(make_shared<OMETIFFWriter>());

          // Set writer options before opening a file
          writer->setMetadataRetrieve(meta);
          writer->setInterleaved(false);

          // Open the file
          writer->setId(filename);

          // Write pixel data
          writePixelData(*writer, std::cout);

          // Explicitly close writer
          writer->close();
          /* writer-example-end */
        }
      else
        {
          std::cerr << "Usage: " << argv[0] << " ome-xml.ome.tiff\n";
          std::exit(1);
        }
    }
  catch (const std::exception& e)
    {
      std::cerr << "Caught exception: " << e.what() << '\n';
      std::exit(1);
    }
  catch (...)
    {
      std::cerr << "Caught unknown exception\n";
      std::exit(1);
    }
}
