//---
//
// License: MIT
//
// Author:  David Burken
//
// Description:
//
// Contains class declaration for ossimDtedTileSource.
//
//---
// $Id$

#include <ossim/imaging/ossimDtedTileSource.h>
#include <ossim/base/ossimConstants.h>
#include <ossim/base/ossimCommon.h>
#include <ossim/base/ossimKeywordNames.h>
#include <ossim/base/ossimTrace.h>
#include <ossim/base/ossimKeywordlist.h>
#include <ossim/base/ossimEllipsoid.h>
#include <ossim/base/ossimStreamFactoryRegistry.h>
#include <ossim/base/ossimDatum.h>
#include <ossim/projection/ossimMapProjection.h>
#include <ossim/projection/ossimEquDistCylProjection.h>
#include <ossim/imaging/ossimImageDataFactory.h>
#include <ossim/imaging/ossimImageData.h>
#include <ossim/imaging/ossimImageGeometry.h>
#include <ossim/imaging/ossimTiffTileSource.h>
#include <ossim/support_data/ossimDtedInfo.h>
#include <cstdlib>
#include <iostream>
using namespace std;

RTTI_DEF1(ossimDtedTileSource, "ossimDtedTileSource", ossimImageHandler)

// Static trace for debugging
static ossimTrace traceDebug("ossimDtedTileSource:debug");


ossimDtedTileSource::ossimDtedTileSource()
   :
      ossimImageHandler(),
      theTile(0),
      m_fileStr(0),
      m_dtedInfo(0),
      theTileWidth(0),
      theTileHeight(0),
      theNumberOfLines(0),
      theNumberOfSamps(0),
      theOffsetToFirstDataRecord(0),
      theDataRecordSize(0),
      theMinHeight(0),
      theMaxHeight(0),
      thePostSpacing(0.0, 0.0),
      theSwapBytesFlag(false)
{
   if (ossim::byteOrder() == OSSIM_LITTLE_ENDIAN)
   {
      theSwapBytesFlag = true;
   }

   // Set the tile size to the default.
   ossimIpt defaultTileSize;
   ossim::defaultTileSize(defaultTileSize);
   theTileWidth  = static_cast<ossim_uint32>(defaultTileSize.x);
   theTileHeight = static_cast<ossim_uint32>(defaultTileSize.y);

   // Construction not complete.  Users should call "open" method.
}

ossimDtedTileSource::~ossimDtedTileSource()
{
   close();
}

ossimString ossimDtedTileSource::getShortName()const
{
   return ossimString("dted");
}

ossimString ossimDtedTileSource::getLongName()const
{
   return ossimString("dted reader");
}

ossimRefPtr<ossimImageData> ossimDtedTileSource::getTile(
   const  ossimIrect& tile_rect, ossim_uint32 resLevel)
{
   if (!theTile.valid())
   {
      allocate(); // First getTile call or after changeTileSize.
      if (!theTile.valid())
      {
         return ossimRefPtr<ossimImageData>(); // Throw exception???
      }
   }

   // Image rectangle must be set prior to calling getTile.
   theTile->setImageRectangle(tile_rect);

   if ( getTile( theTile.get(), resLevel ) == false )
   {
      if (theTile->getDataObjectStatus() != OSSIM_NULL)
      {
         theTile->makeBlank();
      }
   }

   return theTile;
}

bool ossimDtedTileSource::getTile(ossimImageData* result,
                                  ossim_uint32 resLevel)
{
   bool status = false;

   //---
   // Not open, this tile source bypassed, or invalid res level,
   // return a blank tile.
   //---
   if( isOpen() && isSourceEnabled() && isValidRLevel(resLevel) &&
       result && (result->getNumberOfBands() == getNumberOfOutputBands()) )
   {
      result->ref(); // Increment ref count.

      //---
      // Check for overview tile.  Some overviews can contain r0 so always
      // call even if resLevel is 0.  Method returns true on success, false
      // on error.
      //---
      status = getOverviewTile(resLevel, result);

      if ( !status )  // Did not get an overview tile.
      {
         ossimIrect image_rect = getImageRectangle(resLevel);

         ossimIrect tile_rect = result->getImageRectangle();

         //---
         // See if any point of the requested tile is in the image.
         //---
         if ( tile_rect.intersects(image_rect) )
         {
            ossimIrect clip_rect = tile_rect.clipToRect(image_rect);

            if ( !tile_rect.completely_within(clip_rect) )
            {
               // Start with a blank tile.
               result->makeBlank();
            }

            // Load the tile buffer with data from the dted cell.
            status = fillBuffer(tile_rect, clip_rect, result);

         } // End of if ( tile_rect.intersects(image_rect) )
      }

       result->unref(); // Decrement ref count.
   }

   return status;
}

//*******************************************************************
// Private Method:
//*******************************************************************
bool ossimDtedTileSource::fillBuffer(const ossimIrect& tile_rect,
                                     const ossimIrect& clip_rect,
                                     ossimImageData* tile)
{
   //***
   // NOTE:
   // Elevation posts are organized positive line up.
   // The "tile_rect" and "clip_rect" is organized positive line down.
   //***

   const ossim_int32 TILE_WIDTH = tile->getWidth();

   //***
   // Position the file pointer to the first record needed.
   // Posts are organized positive line up so we're going to start at
   // the lower left side of the cell so all reads are going forward in
   // the file.
   //***
   ossim_int32 seek_position = theOffsetToFirstDataRecord +
                         (clip_rect.ul().x * theDataRecordSize);
   seek_position += DATA_RECORD_OFFSET_TO_POST +
                    (theNumberOfLines - 1 - clip_rect.lr().y) * 2;

   //***
   // Get a pointer positioned at the first valid pixel in
   // the tile.  Tiles are organized positive line down.
   //***
   ossim_sint16* d = static_cast<ossim_sint16*>(tile->getBuf());

   // Loop in the longitude or sample direction.
   for (ossim_int32 samp = clip_rect.ul().x; samp <= clip_rect.lr().x; samp++)
   {
      ossim_int32 d_offset = (clip_rect.lr().y - tile_rect.ul().y) *
                       TILE_WIDTH +
                       (samp - tile_rect.ul().x);

      m_fileStr->seekg(seek_position, ios::beg); // Seek to the first post.

      for (ossim_int32 line = clip_rect.lr().y; line >= clip_rect.ul().y; line--)
      {
         // Grab all the post needed from the column.
         ossim_uint16 temp;
         ossim_sint16 s;
         m_fileStr->read((char*)&temp, POST_SIZE);
         s = convertSignedMagnitude(temp);
         d[d_offset] = s;
         d_offset -= TILE_WIDTH;
      }
      seek_position += theDataRecordSize;
   }

   tile->validate();

   return true;
}

ossim_uint32 ossimDtedTileSource::getNumberOfOutputBands()const
{
   return getNumberOfInputBands();
}

ossimIrect
ossimDtedTileSource::getImageRectangle(ossim_uint32 reduced_res_level) const
{
   return ossimIrect(0,
                     0,
                     getNumberOfSamples(reduced_res_level) - 1,
                     getNumberOfLines(reduced_res_level)   - 1);
}

bool ossimDtedTileSource::isOpen()const
{
   return (m_fileStr!=0);
}

void ossimDtedTileSource::close()
{
   m_fileStr.reset();
}

bool ossimDtedTileSource::open()
{
   static const char MODULE[] = "ossimDtedTileSource::open";

   if (traceDebug())
   {
      CLOG << "DEBUG:"
           << "\nAttempting to parse file:  " << theImageFile.c_str()
           << endl;
   }
   std::shared_ptr<ossim::istream> str = ossim::StreamFactoryRegistry::instance()->
      createIstream( theImageFile.c_str(), std::ios_base::in|std::ios_base::binary);

   // Open up the file for reading.
   if (!str)
   {
      theErrorStatus = ossimErrorCodes::OSSIM_ERROR;

	  if(traceDebug())
	  {
		  ossimNotify(ossimNotifyLevel_WARN) << MODULE << " ERROR!"
											 << "\nCould not open file:  " << theImageFile.c_str()
											 << "\nReturning..." << endl;
		  CLOG << "returning with error..." << endl;
	  }
      return false;
   }

   if(!str)
   {
      if (traceDebug())
      {
         CLOG << "DEBUG:"
              << "\nCannot open:  " << theImageFile.c_str()
              << endl;
      }
      str.reset();
      return false;
   }
   std::string connectionString = theImageFile.c_str();
   m_dtedInfo = std::make_shared<ossimDtedInfo>();
   if(!m_dtedInfo->open(str, connectionString))
   {
      close();
      return false;
   }
   m_fileStr = str;
   m_fileStr->clear();
   m_fileStr->seekg(0);

   m_vol.parse(*str);
   m_hdr.parse(*str);
   m_uhl.parse(*str);
   m_dsi.parse(*str);
   m_acc.parse(*str);

   //***
   // Check for errors.  Must have uhl, dsi and acc records.  vol and hdr
   // are for magnetic tape only; hence, may or may not be there.
   //***
   if (m_uhl.getErrorStatus() == ossimErrorCodes::OSSIM_ERROR ||
       m_dsi.getErrorStatus() == ossimErrorCodes::OSSIM_ERROR ||
       m_acc.getErrorStatus() == ossimErrorCodes::OSSIM_ERROR)
   {
      if (traceDebug())
      {
         CLOG << "DEBUG:"
              << "\nError parsing file:  " << theImageFile.c_str()
              << "\nPossibly not a dted file.\n";
      }

      str.reset();
      return false;
   }
   m_fileStr = str;

   // Get the cell specific info needed for later.
   theNumberOfLines = m_uhl.numLatPoints();
   theNumberOfSamps = m_uhl.numLonLines();
   thePostSpacing.x = m_uhl.lonInterval();
   thePostSpacing.y = m_uhl.latInterval();
   theOffsetToFirstDataRecord = m_acc.stopOffset();
   theDataRecordSize = DATA_RECORD_OFFSET_TO_POST +
                       (theNumberOfLines * sizeof(ossim_sint16)) +
                       DATA_RECORD_CHECKSUM_SIZE;

   //---
   // Note:
   // Complete open calls loadMetaData which we have overriden to check for
   // dot omd or dot statistics file.  If not there it will call
   // gatherStatistics which will scan the file for min/max values.
   //---
   completeOpen();

   if (traceDebug())
   {
      CLOG << "DEBUG:"
           << "\nNull pix:  " << getNullPixelValue(0)
           << "\nMin  pix:  " << getMinPixelValue(0)
           << "\nMax  pix:  " << getMaxPixelValue(0)
           << "\ntheNumberOfLines:  " << theNumberOfLines
           << "\ntheNumberOfSamps:  " << theNumberOfSamps
           << "\ntheOffsetToFirstDataRecord:  " << theOffsetToFirstDataRecord
           << "\ntheDataRecordSize:  " << theDataRecordSize << "\n";
   }

   return true;
}

void ossimDtedTileSource::allocate()
{
   //---
   // Set up the tiles.  Note the blank tile will not get initialized to save
   // memory.  This includes setting the min and max pixel value gathered
   // from the statistics.
   //---
   theTile = ossimImageDataFactory::instance()->create(this, this);
   if(theTile.valid())
   {
      theTile->initialize();
   }

   if (traceDebug())
   {
      ossimNotify(ossimNotifyLevel_DEBUG)
         << "DEBUG:"
         << "\ntheTile:\n" << *theTile << "\n";
   }
}

bool ossimDtedTileSource::saveState(ossimKeywordlist& kwl,
                                    const char* prefix) const
{
   // Currently nothing to do here.
   return ossimImageHandler::saveState(kwl, prefix);
}

bool ossimDtedTileSource::loadState(const ossimKeywordlist& kwl,
                                    const char* prefix)
{
   if (ossimImageHandler::loadState(kwl, prefix))
   {
      if (ossimImageHandler::open(theImageFile))
      {
         return true;
      }
   }

   return false;
}

//**************************************************************************************************
// Returns the image geometry object associated with this tile source or NULL if not defined.
// The geometry contains full-to-local image transform as well as projection (image-to-world)
//**************************************************************************************************
ossimRefPtr<ossimImageGeometry> ossimDtedTileSource::getImageGeometry()
{
   if ( theGeometry.valid() )
      return theGeometry;

   // Compute the projection tie point (UL corner of dted cell). The UHL supplies the SW (LL) corner
   // coordinates, so need to compute the UL lat from the cell spacing:
   const ossimDatum* datum = ossimDatumFactory::instance()->wgs84();
   ossim_float64 ul_lat = m_uhl.latOrigin() + (m_uhl.latInterval() * (m_uhl.numLatPoints()-1.0));
   ossim_float64 ul_lon = m_uhl.lonOrigin();
   ossimGpt tie(ul_lat, ul_lon, 0.0, datum);

   //---
   // This code is backed out as it affects the corner.
   // E.g.: image0.ur_lon:  -88.999999999995
   // Should be -89.0
   // Used ossimImageGeometry::::getMetersPerPixel for a center of the image gsd. (drb)
   //
   // We also need to define an origin for the projection as the center of the cell for proper GSD
   // calculation (OLK 01/11: Used to set origin at 0,0 causing incorrect meters GSD to be
   // computed)
   // ossim_float64 mid_lat = m_uhl.latOrigin() + (m_uhl.latInterval()*m_uhl.numLatPoints()/2.0);
   // ossim_float64 mid_lon = m_uhl.lonOrigin() + (m_uhl.lonInterval()*m_uhl.numLonLines()/2.0);
   // ossimGpt origin (mid_lat, mid_lon, 0.0, datum);
   //---
   ossimGpt origin (0.0, 0.0, 0.0, datum);

   // Make an Equidistant Cylindrical projection.
   ossimRefPtr<ossimEquDistCylProjection> eq =
      new ossimEquDistCylProjection(*(datum->ellipsoid()));

      // Set the scale:
   eq->setOrigin(origin);
   eq->setUlTiePoints(tie);
   ossimDpt gsd(m_uhl.lonInterval(), m_uhl.latInterval());
   eq->setDecimalDegreesPerPixel(gsd);

   // Give it to the geometry object.
   ossimRefPtr<ossimProjection> proj = eq.get();

   // Make the geometry:
   theGeometry = new ossimImageGeometry;

   // Set the projection.
   theGeometry->setProjection( proj.get() );

   // Set image things the geometry object should know about.
   initImageParameters( theGeometry.get() );

   return theGeometry;
}

ossimScalarType ossimDtedTileSource::getOutputScalarType() const
{
   return OSSIM_SSHORT16;  // Always signed 16 bit.
}

ossim_uint32 ossimDtedTileSource::getTileWidth() const
{
   return theTileWidth;
}

ossim_uint32 ossimDtedTileSource::getTileHeight() const
{
   return theTileHeight;
}

ossim_uint32 ossimDtedTileSource::getNumberOfLines(ossim_uint32 reduced_res_level) const
{
   if (reduced_res_level == 0)
   {
      return theNumberOfLines;
   }
   else if (theOverview.valid())
   {
      return theOverview->getNumberOfLines(reduced_res_level);
   }

   return 0;
}

ossim_uint32 ossimDtedTileSource::getNumberOfSamples(ossim_uint32 reduced_res_level) const
{
   if (reduced_res_level == 0)
   {
      return theNumberOfSamps;
   }
   else if (theOverview.valid())
   {
      return theOverview->getNumberOfSamples(reduced_res_level);
   }

   return 0;
}

void ossimDtedTileSource::loadMetaData()
{
   ossimKeywordlist kwl;
   const char* min_str = 0;
   const char* max_str = 0;

   ossimFilename f = theImageFile.fileNoExtension();

   // Check for omd file.
   f.setExtension("omd");
   if ( f.exists() )
   {
      kwl.addFile(f);
      min_str = kwl.find("band1.min_value");
      max_str = kwl.find("band1.max_value");
   }

   if ( !min_str || !max_str )
   {
      f.setExtension("statistics");
      if ( f.exists() )
      {
         kwl.addFile(f);
         min_str = kwl.find(ossimKeywordNames::MIN_VALUE_KW);
         max_str = kwl.find(ossimKeywordNames::MAX_VALUE_KW);
      }
   }

   if ( min_str || max_str )
   {
      theMinHeight = atoi(min_str);
      theMaxHeight = atoi(max_str);
   }
   else
   {
      gatherStatistics(false);
   }
}

void ossimDtedTileSource::gatherStatistics(bool writeStatsFile)
{
   // Scan the cell and gather the statistics...
   if ( traceDebug() )
   {
      ossimNotify(ossimNotifyLevel_DEBUG)
         << "ossimDtedTileSource::gatherStatistics() scanning for min/max"
         << "\nThis may take a while...\n";
   }

   // Start off with the min and max pegged.
   theMinHeight =  32767;
   theMaxHeight = -32767;

   // Put the file pointer at the start of the first elevation post.
   m_fileStr->seekg(theOffsetToFirstDataRecord, ios::beg);

   //---
   // Loop through all records and scan for lowest min and highest max.
   // Each record contains a row of latitude points for a given longitude.
   // There are eight bytes in front of the post and four checksum bytes at
   // the end so ignore them.
   //---
   for (ossim_uint32 i=0; i<theNumberOfSamps; i++)  // longitude direction
   {
      m_fileStr->seekg(DATA_RECORD_OFFSET_TO_POST, ios::cur);

      for (ossim_uint32 j=0; j<theNumberOfLines; j++) // latitude direction
      {
         ossim_uint16 temp;
         ossim_sint16 s;
         m_fileStr->read((char*)&temp, POST_SIZE);
         s = convertSignedMagnitude(temp);
         if (s < theMinHeight && s != NULL_PIXEL) theMinHeight = s;
         if (s > theMaxHeight) theMaxHeight = s;
      }

      m_fileStr->seekg(DATA_RECORD_CHECKSUM_SIZE, ios::cur);
   }

   if ( writeStatsFile )
   {
      // Add the stats to the keyword list.
      ossimKeywordlist kwl;
      kwl.add(ossimKeywordNames::MIN_VALUE_KW, theMinHeight);
      kwl.add(ossimKeywordNames::MAX_VALUE_KW, theMaxHeight);

      // Write out the statistics file.
      ossimFilename f = theImageFile.fileNoExtension();
      f.setExtension("statistics");
      kwl.write( f.c_str() );
   }

   if (traceDebug())
   {
      ossimNotify(ossimNotifyLevel_DEBUG)
         << "ossimDtedTileSource::gatherStatistics DEBUG:"
         << "\ntheMinHeight:  " << theMinHeight
         << "\ntheMaxHeight:  " << theMaxHeight
         << "\n";
   }
}

ossim_uint32 ossimDtedTileSource::getImageTileWidth() const
{
   return 0;
}

ossim_uint32 ossimDtedTileSource::getImageTileHeight() const
{
   return 0;
}

ossim_uint32 ossimDtedTileSource::getNumberOfInputBands() const
{
   return 1;
}

double ossimDtedTileSource::getNullPixelValue(ossim_uint32 /* band */)const
{
   if(theMetaData.getNumberOfBands())
   {
      return theMetaData.getNullPix(0);
   }
   return -32767.0;
}
double ossimDtedTileSource::getMinPixelValue(ossim_uint32 band)const
{
   if(theMetaData.getNumberOfBands())
   {
      return theMetaData.getMinPix(band);
   }
   return theMinHeight;
}

 double ossimDtedTileSource::getMaxPixelValue(ossim_uint32 band)const
{
   if(theMetaData.getNumberOfBands())
   {
      return theMetaData.getMaxPix(band);
   }
   return theMaxHeight;
}

void ossimDtedTileSource::getPostSpacing(ossimDpt& postSpacing) const
{
   postSpacing.x = thePostSpacing.x;
   postSpacing.y = thePostSpacing.y;
}

ossimRefPtr<ossimProperty> ossimDtedTileSource::getProperty(
   const ossimString& name)const
{
   // look in base class.
   ossimRefPtr<ossimProperty> result = ossimImageHandler::getProperty(name);

   if (result.valid() == false)
   {
//      ossimDtedInfo info;
//      std::string connectionString = theImageFile.c_str();
      if (m_dtedInfo)
      {
         result = m_dtedInfo->getProperty(name);
      }
   }

   return result;
}

void ossimDtedTileSource::getPropertyNames(
   std::vector<ossimString>& propertyNames) const
{
   ossimImageHandler::getPropertyNames(propertyNames);

   if (m_dtedInfo)
   {
      m_dtedInfo->getPropertyNames(propertyNames);
   }
//   ossimDtedInfo info;
//   if (info.open(theImageFile))
//   {
//      info.getPropertyNames(propertyNames);
//   }
}

const ossimDtedTileSource& ossimDtedTileSource::operator=(const  ossimDtedTileSource& rhs)
{
   return rhs;
}

ossimDtedTileSource::ossimDtedTileSource(const ossimDtedTileSource&)
{
}
