/*
 * Copyright (C) 2005-2017 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "otbDEMHandler.h"
#include "otbMacro.h"

#include <cassert>

#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Woverloaded-virtual"
#pragma GCC diagnostic ignored "-Wshadow"
#include "ossim/elevation/ossimElevManager.h"
#include "ossim/base/ossimGeoidManager.h"
#include "ossim/base/ossimFilename.h"
#include "ossim/base/ossimDirectory.h"
#include "ossim/base/ossimGeoidEgm96.h"
#include "ossim/base/ossimRefPtr.h"
#include <ossim/elevation/ossimImageElevationDatabase.h>
#pragma GCC diagnostic pop
#else
#include "ossim/elevation/ossimElevManager.h"
#include "ossim/base/ossimGeoidManager.h"
#include "ossim/base/ossimFilename.h"
#include "ossim/base/ossimDirectory.h"
#include "ossim/base/ossimGeoidEgm96.h"
#include "ossim/base/ossimRefPtr.h"
#include <ossim/elevation/ossimImageElevationDatabase.h>
#endif



namespace otb
{
/** Initialize the singleton */
DEMHandler::Pointer DEMHandler::m_Singleton = ITK_NULLPTR;

DEMHandler::Pointer DEMHandler::Instance()
{
  if(m_Singleton.GetPointer() == ITK_NULLPTR)
    {
    m_Singleton = itk::ObjectFactory<Self>::Create();

    if(m_Singleton.GetPointer() == ITK_NULLPTR)
      {
      m_Singleton = new DEMHandler;
      }
    m_Singleton->UnRegister();
    }

  return m_Singleton;
}

DEMHandler
::DEMHandler() :
  m_GeoidFile(""),
  m_DefaultHeightAboveEllipsoid(0)
{
  assert( ossimElevManager::instance()!=NULL );

  ossimElevManager::instance()->setDefaultHeightAboveEllipsoid(m_DefaultHeightAboveEllipsoid);
  // Force geoid fallback
  ossimElevManager::instance()->setUseGeoidIfNullFlag(true);
}

void
DEMHandler
::OpenDEMDirectory(const char* DEMDirectory)
{
  assert( ossimElevManager::instance()!=NULL );

  ossimFilename ossimDEMDir( DEMDirectory );

  if (!ossimElevManager::instance()->loadElevationPath(ossimDEMDir))
    {
    // In ossim elevation database factory code, the
    // ossimImageElevationDatabase is explicitly disabled by a #if 0
    // guard, because it causes problem when loading ossim related
    // application. Therefore, we explicitly call
    // ossimImageElevationDatabase here to allow for general elevation
    // images support.
    ossimRefPtr<ossimElevationDatabase> imageElevationDatabase = new ossimImageElevationDatabase;

    if(!imageElevationDatabase->open(DEMDirectory))
      {
      itkExceptionMacro("Failed to open DEM Directory: " << ossimDEMDir);
      }
    else
      {
      otbMsgDevMacro(<< "DEM directory contains general elevation image files: " << ossimDEMDir);
      ossimElevManager::instance()->addDatabase(imageElevationDatabase.get());
      }
    }
}


void
DEMHandler
::ClearDEMs()
{
  assert( ossimElevManager::instance()!=NULL );

  ossimElevManager::instance()->clear();
}


void
DEMHandler
::OpenDEMDirectory(const std::string& DEMDirectory)
{
  OpenDEMDirectory(DEMDirectory.c_str());
}

bool
DEMHandler
::IsValidDEMDirectory(const char* DEMDirectory)
{
  assert( ossimElevManager::instance()!=NULL );

  //Try to load elevation source
  bool result = ossimElevManager::instance()->loadElevationPath(DEMDirectory);

  if (!result)
    {
      // we explicitly call ossimImageElevationDatabase here to allow for general elevation
      // images support and test the open method to check if the directory .
      ossimRefPtr<ossimElevationDatabase> imageElevationDatabase = new ossimImageElevationDatabase;
      result = imageElevationDatabase->open(DEMDirectory);
    }
  return result;
}

bool
DEMHandler
::OpenGeoidFile(const char* geoidFile)
{
  if ((ossimGeoidManager::instance()->findGeoidByShortName("geoid1996")) == ITK_NULLPTR)
    {
    otbMsgDevMacro(<< "Opening geoid: " << geoidFile);
    ossimFilename           geoid(geoidFile);
    ossimRefPtr<ossimGeoid> geoidPtr = new ossimGeoidEgm96(geoid);
    if (geoidPtr->getErrorStatus() == ossimErrorCodes::OSSIM_OK)
      {
      // Ossim does not allow retrieving the geoid file path
      // We therefore must keep it on our side
      m_GeoidFile = geoidFile;
      otbMsgDevMacro(<< "Geoid successfully opened");
      ossimGeoidManager::instance()->addGeoid(geoidPtr);
      geoidPtr.release();

      // The previous flag will be ignored if
      // defaultHeightAboveEllipsoid is not NaN
      assert( ossimElevManager::instance()!=NULL );

      ossimElevManager::instance()->setDefaultHeightAboveEllipsoid(ossim::nan());

      return true;
      }
    else
      {
      otbMsgDevMacro(<< "Failure opening geoid");
      geoidPtr.release();

      itkExceptionMacro( << "Failed to open geoid file: '" << geoidFile << "'" );

      return false;
      }
    }

  return false;
}

bool
DEMHandler
::OpenGeoidFile(const std::string& geoidFile)
{
  return OpenGeoidFile(geoidFile.c_str());
}

double
DEMHandler
::GetHeightAboveMSL(double lon, double lat) const
{
  double   height;
  ossimGpt ossimWorldPoint;

  ossimWorldPoint.lon = lon;
  ossimWorldPoint.lat = lat;

  assert( ossimElevManager::instance()!=NULL );

  height = ossimElevManager::instance()->getHeightAboveMSL(ossimWorldPoint);

  return height;
}

double
DEMHandler
::GetHeightAboveMSL(const PointType& geoPoint) const
{
  return GetHeightAboveMSL(geoPoint[0], geoPoint[1]);
}

double
DEMHandler
::GetHeightAboveEllipsoid(double lon, double lat) const
{
  double   height;
  ossimGpt ossimWorldPoint;

  ossimWorldPoint.lon = lon;
  ossimWorldPoint.lat = lat;

  assert( ossimElevManager::instance()!=NULL );

  height = ossimElevManager::instance()->getHeightAboveEllipsoid(ossimWorldPoint);

  return height;
}

double
DEMHandler
::GetHeightAboveEllipsoid(const PointType& geoPoint) const
{
  return GetHeightAboveEllipsoid(geoPoint[0], geoPoint[1]);
}

void
DEMHandler
::SetDefaultHeightAboveEllipsoid(double h)
{
  // Ossim does not allow retrieving the default height above
  // ellipsoid We therefore must keep it on our side
  m_DefaultHeightAboveEllipsoid = h;

  assert( ossimElevManager::instance()!=NULL );

  ossimElevManager::instance()->setDefaultHeightAboveEllipsoid(h);
}

double
DEMHandler
::GetDefaultHeightAboveEllipsoid() const
{
  // Ossim does not allow retrieving the default height above
  // ellipsoid We therefore must keep it on our side
  return m_DefaultHeightAboveEllipsoid;
}

unsigned int
DEMHandler
::GetDEMCount() const
{
  assert( ossimElevManager::instance()!=NULL );

  return ossimElevManager::instance()->getNumberOfElevationDatabases();
}

std::string DEMHandler::GetDEMDirectory(unsigned int idx) const
{
  std::string demDir = "";

  if(ossimElevManager::instance()->getNumberOfElevationDatabases() > 0)
    {
    assert( ossimElevManager::instance()!=NULL );

    demDir = ossimElevManager::instance()->getElevationDatabase(idx)->getConnectionString().string();
    }
  return demDir;
}

std::string DEMHandler::GetGeoidFile() const
{
  // Ossim does not allow retrieving the geoid file path
  // We therefore must keep it on our side
  return m_GeoidFile;
}

void
DEMHandler
::PrintSelf(std::ostream& os, itk::Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "DEMHandler" << std::endl;
}

} // namespace otb
