/* ImLib3D
 * Copyright (c) 2001, ULP-IPB Strasbourg.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#ifndef _Image3DSet_hxx
#define _Image3DSet_hxx
// -----------------------------------------------------------------
// Image3DSet Class functions:
// -----------------------------------------------------------------

template <class ImageType>
ostream& operator<<(ostream& s, const Image3DSet<ImageType>& set)
{
	s << "FileList size: " << set.fileNames.size() << endl;
	s << "Statuslist size: " << set.status.size() << endl;
	s << "ImageType*list size: " << set.images.size() << endl;
	s << "LastTimeUsedlist size: " << set.lastTimeUsed.size() << endl;
	s << "nbImages: " << set.nbImages << endl;
	for (uint n=0; n<set.fileNames.size(); n++)
	{
		s <<"Filename: " << set.fileNames[n] << "     Status: " << set.status[n] << endl;
	}
	return s;
}

template <class ImageType>
Image3DSet<ImageType>::Image3DSet(const vector<string> & _fileNames,bool _fileIsReadOnly,bool _isMemOnly):
	fileNames(_fileNames),
	nbImages(_fileNames.size()),
	fileIsReadOnly(_fileIsReadOnly),
	isMemOnly(_isMemOnly),
	lruGap(0),
	startTime(DTime())
{
	images.assign(nbImages,(ImageType*)NULL);
	status.assign(nbImages,ON_FILE);
	lastTimeUsed.assign(nbImages,0);
}

template <class ImageType>
Image3DSet<ImageType>::Image3DSet(bool _fileIsReadOnly,bool _isMemOnly):
	nbImages(0),
	fileIsReadOnly(_fileIsReadOnly),
	isMemOnly(_isMemOnly),
	lruGap(0),
	startTime(DTime())
{
	;
}

template <class ImageType>
Image3DSet<ImageType>::Image3DSet(const Image3DSet<ImageType>& _other):
	fileNames(_other.fileNames),
	nbImages(_other.Size()),
	fileIsReadOnly(_other.fileIsReadOnly),
	isMemOnly(_other.isMemOnly),
	lruGap(0),
	startTime(DTime())
{
	images.assign(nbImages,(ImageType*)NULL);
	status.assign(nbImages,ON_FILE);
	lastTimeUsed.assign(nbImages,0);
}


template <class ImageType>
Image3DSet<ImageType>::~Image3DSet()
{
	if(!isMemOnly && !fileIsReadOnly){FlushAllToDisk();}
	DeleteImages();
}

template <class ImageType>
void
Image3DSet<ImageType>::Clear(bool _fileIsReadOnly,bool _isMemOnly)
{
	if(!isMemOnly && !fileIsReadOnly){FlushAllToDisk();}
	DeleteImages();
	fileIsReadOnly = _fileIsReadOnly;
	isMemOnly = _isMemOnly;
	fileNames.clear();
	status.clear();
	lastTimeUsed.clear();
	nbImages = 0;
	startTime = DTime();
	lruGap = 0;
}

//! Loads one image into memory
template <class ImageType>
void 
Image3DSet<ImageType>::LoadImage(int i)
{
//	mprintf("Image3DSet::LoadImage: loading image %d\n",i);
	mprintf("Image3DSet::LoadImage: loading image %d:\"%s\"\n",i,fileNames[i].c_str());
	if(!Fits(i)){FlushToDisk(LeastRecentlyUsed());}
	if(status[i]==IN_MEM){ThrowError("Image3DSet::LoadImage image already in mem? image:%d",i);}
	images[i]=new ImageType(fileNames[i]);
	status[i]=IN_MEM;
}
//! Finds lru image and flushes it to disk
template <class ImageType>
int
Image3DSet<ImageType>::LeastRecentlyUsed()
{
	double lruTime=lastTimeUsed[0];
	int n=0;
	for(int i=1;i<nbImages;i++)
	{
		if(lastTimeUsed[i]<lruTime){n=i;lruTime=lastTimeUsed[i];}
	}
	return n;
}

//! Saves one image into disk
template <class ImageType>
void Image3DSet<ImageType>::FlushToDisk(int i)
{
	if(isMemOnly){ThrowError("Image3DSet::SaveImage memOnly!");}
	if(status[i]==ON_FILE){ThrowError("Image3DSet::SaveImage image already on file? image:%d",i);}	
	images[i]->WriteToFile(fileNames[i]);
	delete images[i];
	images[i]=NULL;
	status[i]=ON_FILE;
}

//! Loads all images into memory
template <class ImageType>
void Image3DSet<ImageType>::LoadAllImages()
{
	for (int i=0; i<nbImages; i++){LoadImage(i);}
}

//! Saves all images into disk
template <class ImageType>
void 
Image3DSet<ImageType>::FlushAllToDisk()
{
	for (int i=0; i<nbImages; i++){FlushToDisk(i);}
}

//! Returns a pointer to the contained container-element at position n (start indexing at 0)
/*!
  <long-description>
  return value: NULL if position is outside scope or the container couldn't be achieved.
  \param position   
*/
template <class ImageType>
ImageType &
Image3DSet<ImageType>::GetImage(int position)
{
	if(position>=nbImages)
	{ThrowError("Image3DSet::GetImage: pos=%d is pastend of image set(%d)",position,nbImages);}

	if(status[position]!=IN_MEM)
	{//! Image is on disk, must load it!
		LoadImage(position);
	}
	if(!((lruGap++)%10)){lastTimeUsed[position]=DTime(startTime);}
	return *(images[position]);
}

template <class ImageType>
void Image3DSet<ImageType>::DeleteImages()
{
	for(uint i=0;i<images.size();i++)
	{
		if(images[i]){delete images[i];}
	}
}


//!Add image to set, optional file argument, returns position in set
template <class ImageType>
int
Image3DSet<ImageType>::AddImage(ImageType *image,const string &fileName)
{
//  	if (fileIsReadOnly && !isMemOnly)
//  		ThrowError("Image3DSet::AddImage Error: Trying to add image to a set that is ReadOnly\n");
	nbImages++;
	fileNames.push_back(fileName);
	images.push_back(image);
	status.push_back(IN_MEM);
	lastTimeUsed.push_back(DTime(startTime));
	if(
		(int)fileNames.size()   !=nbImages ||
		(int)images.size()      !=nbImages ||
		(int)status.size()      !=nbImages ||
		(int)lastTimeUsed.size()!=nbImages  )
	{
		ThrowError("Image3DSet::AddImage:incoherence in vect sizes");
	}
	return nbImages-1;
}

template<class ImageType>
int 
Image3DSet<ImageType>::GetPosition(const string &filename)
{
	int n = 0;
	vector<string>::iterator plist;
	plist = fileNames.begin();
	while(((*plist) != filename)&& (n < nbImages))
	{
		plist++;
		n++;
	};
	if (n >= nbImages) //Filename not found
		throw runtime_error("Image3DSet::GetPosition filename not found in filelist: " + filename);
	return n;
}


//! Removes first instance of the pointer associated with the filename
/*!
  <long-description> Returns the pointer that has been removed from the list. The user is responsible for deleting this pointer. If the Image couldn't be found in the filelist, the NULL pointer will be returned (don't delete). If the Image is not in memory it will be loaded.
  
  \param filename   
*/
template <class ImageType>
ImageType* Image3DSet<ImageType>::RemoveImage(const string &filename)
{
	return RemoveImage(GetPosition(filename));
}

template <class ImageType>
ImageType* Image3DSet<ImageType>::RemoveImage(int position)
{
	if (nbImages <= position)
		return NULL;
	// Erasing  list entries
	// Erasing Imagepointer from pointer list
	ImageType* rPointer;
	if (status[position] != IN_MEM){LoadImage(position);}

	rPointer=images[position];
	images.erase(images.begin()+position);

	// Erasing filename from FileList if present
	if (fileNames.size()){fileNames.erase(fileNames.begin()+position);}

	//Erasing status entry from status list
	status.erase(status.begin()+position);

	//Erasing entry from lru list
	lastTimeUsed.erase(lastTimeUsed.begin()+position);

	nbImages--;
	return rPointer;
}

//! Returns true if all images are of the same size, else false. Warning: all images will be loaded into memory if necessary!
template<class ImageType>
bool Image3DSet<ImageType>::IsAllSameSize()
{
	if (!nbImages)
		return true;
	Size3D sizeOfFirst = GetImage(0).Size();
	for (int i=1; i<nbImages; i++)
	{
		Size3D sizeOfNext = GetImage(i).Size();
		if (sizeOfFirst != sizeOfNext)
			return false;
	}
	return true;
}

//! Checks if the contents of some files by accident could be the same. The function return a list of filenames if it found images that are alike (operator==), where one filename might occur several times. If the set is mem only, the numbers (1, 2 ..)will be pushed_back as strings.
template<class ImageType>
vector<string>
Image3DSet<ImageType>::ImagesThatAreAlike()
{
	int i1, i2;
	vector<string> ret;
	for (i1 = 0; i1 < Size(); i1++)
	{
		bool firstMatch = true;
		for (i2 = i1 + 1; i2 < Size(); i2++)
		{
			if (GetImage(i1) == GetImage(i2))
			{
				if (firstMatch)
				{
					if (isMemOnly)
						ret.push_back(SPrintf("%d", i1));
					else
						ret.push_back(GetFilename(i1));
					firstMatch = false;
				}
				if (isMemOnly)
					ret.push_back(SPrintf("%d", i2));
				else
					ret.push_back(GetFilename(i2));
			}
		}
	}
	return ret;
}
#endif // _Image3DSet_hxx
