//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "ITKProcess.hpp"

#include <itkImportImageFilter.h>
#include <itkSmoothingRecursiveGaussianImageFilter.h>
#include <itkImageSliceConstIteratorWithIndex.h>
#include <itkImageRegionConstIterator.h>
#include <itkImageFileWriter.h>
#include <itkAdaptImageFilter.h>

#include <Information.hpp>

namespace mgx 
{
  SImageConverter::SImageConverter()
  {
    importer = ImportFilterType::New();
    adapter = AdapterType::New();
  
    adapter->SetInput(importer->GetOutput());
  }
  
  SImageConverter::~SImageConverter()
  {
    importer->Delete();
    adapter->Delete();
  }
  
  void SImageConverter::SetStore(const Store* s)
  {
    store = s;
  
    const Stack* stack = store->stack();
  
    UImageType::SizeType size;
    size[0] = stack->size().x();
    size[1] = stack->size().y();
    size[2] = stack->size().z();
  
    UImageType::IndexType start;
    start[0] = start[1] = start[2] = 0;
  
    UImageType::RegionType region;
    region.SetSize(size);
    region.SetIndex(start);
  
    Information::out << "Size of the input image: " << region.GetSize(0) << "x" << region.GetSize(1) << "x"
                     << region.GetSize(2) << endl;
  
    importer->SetRegion(region);
  
    double spacing[3];
    spacing[0] = stack->step().x();
    spacing[1] = stack->step().y();
    spacing[2] = stack->step().z();
  
    importer->SetSpacing(spacing);
  
    double origin[3];
    origin[0] = stack->origin().x();
    origin[1] = stack->origin().y();
    origin[2] = stack->origin().z();
  
    importer->SetOrigin(origin);
  
    const bool importFilterWillDeleteTheInputBuffer = false;
  
    typedef UImageType::PixelType PixelType;
  
    PixelType* pixelData = const_cast<PixelType*>(store->data().data());
  
    unsigned long totalNumberOfPixels = store->size();
  
    importer->SetImportPointer(pixelData, totalNumberOfPixels, importFilterWillDeleteTheInputBuffer);
  }
  
  void SImageConverter::Update() {
    adapter->Update();
  }
  
  SImageConverter::Pointer SImageConverter::New() {
    return Pointer(new SImageConverter());
  }
  
  itk::SmartPointer<itk::LightObject> SImageConverter::CreateAnother() const
  {
    return itk::SmartPointer<itk::LightObject>(new SImageConverter());
  }
  
  const char* SImageConverter::GetNameOfClass() const {
    return "SImageConverter";
  }
  
  SImageType::Pointer SImageConverter::GetOutput() {
    return adapter->GetOutput();
  }
  
  bool SImageConverter::TransferImage(Stack* stack, Store* store, SImageType::ConstPointer image)
  {
    SImageType::RegionType region = image->GetBufferedRegion();
  
    SImageType::RegionType::SizeType img_size = region.GetSize();
  
    SImageType::SpacingType img_spacing = image->GetSpacing();
  
    SImageType::PointType origin = image->GetOrigin();
  
    Point3u size(img_size[0], img_size[1], img_size[2]);
    Point3f step(img_spacing[0], img_spacing[1], img_spacing[2]);
  
    stack->setStep(step);
    stack->setSize(size);
    stack->setOrigin(Point3f(origin));
  
    return SImageConverter::TransferImage((const Stack*)stack, store, image);
  }
  
  bool SImageConverter::TransferImage(const Stack*, Store* store, SImageType::ConstPointer image)
  {
    typedef itk::AdaptImageFilter<SImageType, UImageType, SignedToUnsignedAccessor> AdapterType;
  
    AdapterType::Pointer adapter = AdapterType::New();
  
    adapter->SetInput(image);
  
    adapter->Update();
  
    UImageType::ConstPointer result = adapter->GetOutput();
  
    UImageType::RegionType region = result->GetBufferedRegion();
  
    SImageType::RegionType::SizeType img_size = region.GetSize();
  
    uint ss = img_size[0] * img_size[1] * img_size[2];
    if(ss != store->size())
      return false;
  
    ushort* out = store->data().data();
  
    typedef itk::ImageRegionConstIterator<UImageType> IteratorType;
  
    IteratorType it(result, region);
  
    it.GoToBegin();
    while(!it.IsAtEnd()) {
      *out++ = it.Get();
      ++it;
    }
  
    return true;
  }
  
  FImageConverter::FImageConverter()
  {
    importer = ImportFilterType::New();
    adapter = AdapterType::New();
  
    adapter->SetInput(importer->GetOutput());
  }
  
  FImageConverter::~FImageConverter()
  {
    importer->Delete();
    adapter->Delete();
  }
  
  void FImageConverter::SetStore(const Store* s)
  {
    store = s;
  
    const Stack* stack = store->stack();
  
    UImageType::SizeType size;
    size[0] = stack->size().x();
    size[1] = stack->size().y();
    size[2] = stack->size().z();
  
    UImageType::IndexType start;
    start[0] = start[1] = start[2] = 0;
  
    UImageType::RegionType region;
    region.SetSize(size);
    region.SetIndex(start);
  
    Information::out << "Size of the input image: " << region.GetSize(0) << "x" << region.GetSize(1) << "x"
                     << region.GetSize(2) << endl;
  
    importer->SetRegion(region);
  
    double spacing[3];
    spacing[0] = stack->step().x();
    spacing[1] = stack->step().y();
    spacing[2] = stack->step().z();
  
    importer->SetSpacing(spacing);
  
    double origin[3];
    origin[0] = stack->origin().x();
    origin[1] = stack->origin().y();
    origin[2] = stack->origin().z();
  
    importer->SetOrigin(origin);
  
    const bool importFilterWillDeleteTheInputBuffer = false;
  
    typedef UImageType::PixelType PixelType;
  
    PixelType* pixelData = const_cast<PixelType*>(store->data().data());
  
    unsigned long totalNumberOfPixels = store->size();
  
    importer->SetImportPointer(pixelData, totalNumberOfPixels, importFilterWillDeleteTheInputBuffer);
  }
  
  void FImageConverter::Update() {
    adapter->Update();
  }
  
  FImageConverter::Pointer FImageConverter::New() {
    return Pointer(new FImageConverter());
  }
  
  itk::SmartPointer<itk::LightObject> FImageConverter::CreateAnother() const
  {
    return itk::SmartPointer<itk::LightObject>(new FImageConverter());
  }
  
  const char* FImageConverter::GetNameOfClass() const {
    return "FImageConverter";
  }
  
  FImageType::Pointer FImageConverter::GetOutput() {
    return adapter->GetOutput();
  }
  
  bool FImageConverter::TransferImage(Stack* stack, Store* store, FImageType::ConstPointer image)
  {
    FImageType::RegionType region = image->GetBufferedRegion();
  
    FImageType::RegionType::SizeType img_size = region.GetSize();
  
    FImageType::SpacingType img_spacing = image->GetSpacing();
  
    FImageType::PointType origin = image->GetOrigin();
  
    Point3u size(img_size[0], img_size[1], img_size[2]);
    Point3f step(img_spacing[0], img_spacing[1], img_spacing[2]);
  
    stack->setStep(step);
    stack->setSize(size);
    stack->setOrigin(Point3f(origin));
  
    return FImageConverter::TransferImage((const Stack*)stack, store, image);
  }
  
  bool FImageConverter::TransferImage(const Stack*, Store* store, FImageType::ConstPointer image)
  {
    typedef itk::AdaptImageFilter<FImageType, UImageType, FloatToUnsignedAccessor> AdapterType;
  
    AdapterType::Pointer adapter = AdapterType::New();
  
    adapter->SetInput(image);
  
    adapter->Update();
  
    UImageType::ConstPointer result = adapter->GetOutput();
  
    UImageType::RegionType region = result->GetBufferedRegion();
  
    FImageType::RegionType::SizeType img_size = region.GetSize();
  
    uint ss = img_size[0] * img_size[1] * img_size[2];
    if(ss != store->size())
      return false;
  
    ushort* out = store->data().data();
  
    typedef itk::ImageRegionConstIterator<UImageType> IteratorType;
  
    IteratorType it(result, region);
  
    it.GoToBegin();
    while(!it.IsAtEnd()) {
      *out++ = it.Get();
      ++it;
    }
  
    return true;
  }
  
  UImageConverter::UImageConverter() {
    importer = ImportFilterType::New();
  }
  
  UImageConverter::~UImageConverter() {
    importer->Delete();
  }
  
  void UImageConverter::SetStore(const Store* s)
  {
    store = s;
  
    const Stack* stack = store->stack();
  
    UImageType::SizeType size;
    size[0] = stack->size().x();
    size[1] = stack->size().y();
    size[2] = stack->size().z();
  
    UImageType::IndexType start;
    start[0] = start[1] = start[2] = 0;
  
    UImageType::RegionType region;
    region.SetSize(size);
    region.SetIndex(start);
  
    Information::out << "Size of the input image: " << region.GetSize(0) << "x" << region.GetSize(1) << "x"
                     << region.GetSize(2) << endl;
  
    importer->SetRegion(region);
  
    itk::Vector<double, 3> spacing;
    spacing[0] = stack->step().x();
    spacing[1] = stack->step().y();
    spacing[2] = stack->step().z();
  
    importer->SetSpacing(spacing);
  
    spacing = importer->GetSpacing();
  
    itk::Point<double, 3> origin;
    origin[0] = stack->origin().x();
    origin[1] = stack->origin().y();
    origin[2] = stack->origin().z();
  
    importer->SetOrigin(origin);
  
    origin = importer->GetOrigin();
  
    const bool importFilterWillDeleteTheInputBuffer = false;
  
    typedef UImageType::PixelType PixelType;
  
    PixelType* pixelData = const_cast<PixelType*>(store->data().data());
  
    unsigned long totalNumberOfPixels = store->size();
  
    importer->SetImportPointer(pixelData, totalNumberOfPixels, importFilterWillDeleteTheInputBuffer);
  }
  
  void UImageConverter::Update() {
    importer->Update();
  }
  
  UImageConverter::Pointer UImageConverter::New() {
    return Pointer(new UImageConverter());
  }
  
  itk::SmartPointer<itk::LightObject> UImageConverter::CreateAnother() const
  {
    return itk::SmartPointer<itk::LightObject>(new UImageConverter());
  }
  
  const char* UImageConverter::GetNameOfClass() const {
    return "UImageConverter";
  }
  
  UImageType::Pointer UImageConverter::GetOutput() {
    return importer->GetOutput();
  }
  
  bool UImageConverter::TransferImage(Stack* stack, Store* store, UImageType::ConstPointer image)
  {
    UImageType::RegionType region = image->GetBufferedRegion();
  
    UImageType::RegionType::SizeType img_size = region.GetSize();
  
    UImageType::SpacingType img_spacing = image->GetSpacing();
  
    UImageType::PointType origin = image->GetOrigin();
  
    Point3u size(img_size[0], img_size[1], img_size[2]);
    Point3f step(img_spacing[0], img_spacing[1], img_spacing[2]);
  
    stack->setStep(step);
    stack->setSize(size);
    stack->setOrigin(Point3f(origin));
  
    return UImageConverter::TransferImage((const Stack*)stack, store, image);
  }
  
  bool UImageConverter::TransferImage(const Stack*, Store* store, UImageType::ConstPointer image)
  {
    UImageType::RegionType region = image->GetBufferedRegion();
  
    UImageType::RegionType::SizeType img_size = region.GetSize();
  
    uint ss = img_size[0] * img_size[1] * img_size[2];
    if(ss != store->size())
      return false;
  
    ushort* out = store->data().data();
  
    typedef itk::ImageRegionConstIterator<UImageType> IteratorType;
  
    IteratorType it(image, region);
  
    it.GoToBegin();
    while(!it.IsAtEnd()) {
      *out++ = it.Get();
      ++it;
    }
  
    return true;
  }
}
