
/*************************************************************************
*
* ADOBE CONFIDENTIAL
* ___________________
*
*  Copyright 2010 Adobe Systems Incorporated
*  All Rights Reserved.
*
* NOTICE:  All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any.  The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*
* $DateTime$
*
* Adobe Output Module 4.0
*
* ImageCache written by Adobe System China.
* 
*
**************************************************************************/

AOM.ImageCache = function() 
{
	this.data = new Array();
	this.fileNumber = 0;
	this.tempImageCacheFolder = Folder.userData + "/Adobe/Bridge " + AOM.CS_VERSION + "/Adobe Output Module/ImageCache/";
	var tempImageCacheFolder = Folder(this.tempImageCacheFolder);
	if (!tempImageCacheFolder.exists) {
		tempImageCacheFolder.create();
	}
	this.initialize();
}


AOM.ImageCache.getInstance = function()
{
	if (AOM.ImageCache.Instance == undefined) {
		AOM.ImageCache.Instance = new AOM.ImageCache();
	}
	return AOM.ImageCache.Instance;
}

AOM.ImageCache.getAlignedNumber = function(num, length)
{
	var s = new String(num);
	if(s.length >= length)
		return s;

	var prefix = "";
	for(var i = 0; i < length - s.length; ++i)
		prefix += "0";

	return prefix + s;
}

AOM.ImageCache.getDateString = function(date)
{
	return AOM.ImageCache.getAlignedNumber(date.getFullYear(), 4) + "-" +
		   AOM.ImageCache.getAlignedNumber(date.getMonth() + 1, 2) + "-" +
		   AOM.ImageCache.getAlignedNumber(date.getDate(), 2) + "-" +
		   AOM.ImageCache.getAlignedNumber(date.getHours(), 2) + ":" +
		   AOM.ImageCache.getAlignedNumber(date.getMinutes(), 2) + ":" +
		   AOM.ImageCache.getAlignedNumber(date.getSeconds(), 2) + "." +
		   AOM.ImageCache.getAlignedNumber(date.getMilliseconds(), 3);
}

AOM.ImageCache.prototype.initialize = function()
{
	// Turn off cross-session cache for this release.
	//
	// this.restore();
}

AOM.ImageCache.prototype.persist = function() 
{
	if(this.data == undefined)
		return true;
    
	var file = new File(this.tempImageCacheFolder + File.encode("imageCache.json"));
	
	if(!file.open("w"))
		return AOM.CS.Log.writeError("AOM.ImageCache.persist - could not open file(" + file.fsName + ")");
	
	var writeItem = function(item)
    {
		file.writeln("{");
		file.writeln("\toriginFilePath:" + "\"" + File.encode(item.originFilePath) + "\",");
		file.writeln("\toriginSize:" + item.originSize + ",");
		file.writeln("\tmodifiedDate:" + "\"" + item.modifiedDate + "\",");
		file.writeln("\ttempFileName:" + "\"" + File.encode(item.tempFileName) + "\",");
		file.writeln("\trotation:" + item.rotation + ",");
		file.writeln("\tcolorProfilePreserved:" + item.colorProfilePreserved + ",");
		file.writeln("\thasResized:" + item.hasResized + ",");
		file.writeln("\tcanOpenFile:" + item.canOpenFile + ",");
		file.writeln("\theight:" + "\"" + item.height + "\",");
		file.writeln("\twidth:" + "\"" + item.width + "\",");
		file.writeln("\tlastVisitedTime:" + "\"" + item.lastVisitedTime + "\",");
		file.writeln("\tlatestOperation:" + "\"" +item.latestOperation + "\"");
		file.writeln("},");
	}
	
	file.writeln("AOM.ImageCache.persistedData = [");
	var data = this.data;
	var i;
	for(i = 0; i < data.length; ++i)
		writeItem(data[i]);

	file.writeln("];\n");
	file.close();
	return true;
}

AOM.ImageCache.prototype.restore = function()
{
	alert("Cross-session cache persistance should be turned off.");

	this.data = new Array();
	var file = new File(this.tempImageCacheFolder + File.encode("imageCache.json"));
	file.open("r");
	var json = file.read();
	file.close();
	AOM.ImageCache.persistedData = undefined;
	eval(json);

	if (AOM.ImageCache.persistedData == undefined || !(AOM.ImageCache.persistedData instanceof Array))
		return;

	try {
		var data = AOM.ImageCache.persistedData;
		for (var i = 0; i < data.length; ++i) {    
			var temp = data[i];
			temp.originFilePath = File.decode (temp.originFilePath);
			temp.tempFileName = File.decode (temp.tempFileName);
			this.addItem(temp);
		}
	} catch(e) {
		AOM.CS.Log.writeError("Error happens while setting cache values from Cache.json.");
	}
	
	AOM.ImageCache.persistedData = undefined;
}

AOM.ImageCache.prototype.calculateDemand = function(thumbnail, demandInfo, imageHeight, imageWidth)
{
	var result = {};
	result.colorProfilePreserved = demandInfo.colorProfilePreserved;
	result.rotation = 0;

	var demandRatio = demandInfo.width / demandInfo.height;
	var imageRatio = imageWidth / imageHeight;
	
	if(demandInfo.rotateToFit && imageRatio != 1.0) {
		var tallImage = imageRatio < 1.0;
		var demandTallImage = (demandInfo.width / demandInfo.height) < 1.0;

		if(tallImage != demandTallImage) {
			var swapBorder = imageWidth;
			imageWidth = imageHeight;
			imageHeight = swapBorder;
			switch (thumbnail.rotation)
			{
				case 90:case 180:
					result.rotation = 90;
					break;
				case 0:case -90:default:
					result.rotation = -90;
					break;
			}
			imageRatio = imageWidth / imageHeight;
		} else {
			switch (thumbnail.rotation)
			{
				case -90:case 180:
					result.rotation = 180;
					break;
				case 0:case 90:default:
					result.rotation = 0;
					break;
			}
		}
		if(demandRatio > imageRatio) {
			result.height = demandInfo.height;
			result.width = Math.round(demandInfo.height * imageRatio);
		} else {
			result.width = demandInfo.width;
			result.height = Math.round(demandInfo.width / imageRatio);
		}
	} else {
		if(thumbnail.rotation == 90 || thumbnail.rotation == -90) {
			var swapBorder = imageWidth;
			imageWidth = imageHeight;
			imageHeight = swapBorder;
			imageRatio = imageWidth / imageHeight;
		}
		result.rotation = thumbnail.rotation;
        
		if(demandRatio > imageRatio) {
			result.height = demandInfo.height;
			result.width = Math.round(demandInfo.height * imageRatio);
		} else {
			result.width = demandInfo.width;
			result.height = Math.round(demandInfo.width / imageRatio);
		}
	}
	
	return result;
}


AOM.ImageCache.prototype.findOriginals = function(thumbnail)
{
	var dateStr = AOM.ImageCache.getDateString(thumbnail.lastModifiedDate);
	var result = new Array();

	for (var i = 0; i < this.data.length; ++i) {
		var current = this.data[i];
		if (current.originFilePath == thumbnail.path) {
			var fileLength = -1;
			if (thumbnail.spec.length != undefined)
				fileLength = thumbnail.spec.length;

			// If find the original file changed, purge all cache items with
			// the different date and length because they are cached for and older
			// version and therefore unlikely to be hit again.
			if (current.modifiedDate == dateStr &&
				current.originSize == fileLength)
				result.push(i);
			else
				this.releaseByIndex(i--);
		}
	}
	return result;
}

AOM.ImageCache.isQualified = function(cacheFileHeight, cacheFileWidth, demandedFileHeight, demandedFileWidth, accuracy)
{
	if(cacheFileHeight / demandedFileHeight < accuracy &&
		demandedFileHeight / cacheFileHeight < accuracy) {
		return true;
	}
	return false;
}

AOM.ImageCache.prototype.findDemand = function(thumbnail, demandInfo, accuracy, jpegAccuracy)
{
	var itemInfo = {};
	itemInfo.cacheImagePath = undefined;
	itemInfo.cahceImageName = undefined;
	itemInfo.index = -1;
	itemInfo.originalFileSupported = true;

	var originalMatch = this.findOriginals(thumbnail);

	for(var i = 0; i < originalMatch.length; ++i) {
		var temp = this.data[originalMatch[i]];
		
		if(temp.canOpenFile == false) {
		   temp.lastVisitedTime = AOM.ImageCache.getDateString(new Date());
			itemInfo.originalFileSupported = false;
			itemInfo.index = i;
			temp.latestOperation = 2;
			return itemInfo;
		}

		var height, width;
		if(temp.rotation == 90 || temp.rotation == -90) {
			height = temp.width;
			width = temp.height;
		} else {
			height = temp.height;
			width = temp.width;
		}
		
		var result = this.calculateDemand(thumbnail, demandInfo, height, width);
	
		if(temp.rotation == result.rotation &&
		   temp.colorProfilePreserved == result.colorProfilePreserved) {
			var cacheHit = false;
			if(demandInfo.fullResolution) {
				if (!temp.hasResized)
					cacheHit = true;
			} else if(result.height > temp.height && !temp.hasResized) {
				cacheHit = true;
			} else {
				if (accuracy == 1) {
					//match when the dimention is in the inaccuracy of 1 pixel
					if(result.height - temp.height <=  1 && temp.height - result.height <= 1)
						cacheHit = true;
				} else {
					//match when ratio is between 1 and accuracy
					if(AOM.ImageCache.isQualified(temp.height, temp.width, result.height, result.width, accuracy))
						cacheHit = true;
				}
			}
			if (Math.abs(demandInfo.jpegQuality - temp.jpegQuality) > jpegAccuracy)
				cacheHit = false;
			if(cacheHit) {
				temp.lastVisitedTime = AOM.ImageCache.getDateString(new Date());
				itemInfo.cacheImagePath = this.tempImageCacheFolder + File.encode(temp.tempFileName) + ".jpg";
				itemInfo.cacheImageName = temp.tempFileName;
				itemInfo.index = i;
				itemInfo.height = temp.height;
				itemInfo.width = temp.width;
				temp.latestOperation = 2;
				return itemInfo;
			}
		}
	}

	return itemInfo;
}

// Maximal number of cache items.
AOM.ImageCache.SaveCacheNumber = 500;

AOM.ImageCache.sortDateAscending = function (a, b)
{
	if (a.latestOperation > b.latestOperation) {
		return -1;
	}
	if (a.latestOperation < b.latestOperation) {
		return 1;
	}
	if (a.lastVisitedTime > b.lastVisitedTime) {
		return 1;
	}
	if (a.lastVisitedTime < b.lastVisitedTime) {
		return -1;
	}
	return 0;
}
	
// Delete the most aged cache items when the number of items is beyond AOM.Cache.SaveCacheNumber.
AOM.ImageCache.prototype.update = function()
{
	if (this.data.length > AOM.ImageCache.SaveCacheNumber) {
		this.data.sort(AOM.ImageCache.sortDateAscending);
		while (true) {
			if (this.data[0].latestOperation != 0) {
				this.releaseByIndex(0);
				if (this.data.length <= AOM.ImageCache.SaveCacheNumber * 0.7)
					break;
			} else {
				if (this.data.length <= AOM.ImageCache.SaveCacheNumber)
					break;
				else
					this.releaseByIndex(0);
			}        
		}
	}

	if (AOM.ImageCache.Debug == true) {
		this.persist();
	}

	for (i = 0; i < this.data.length; ++i) {
		this.data[i].latestOperation = 1;
	}
}

AOM.ImageCache.sortDateDescending = function (a, b)
{
	if (a.latestOperation > b.latestOperation) {
		return 1;
	}
	if (a.latestOperation < b.latestOperation) {
		return -1;
	}
	if (a.lastVisitedTime > b.lastVisitedTime) {
		return -1;
	}
	if (a.lastVisitedTime < b.lastVisitedTime) {
		return 1;
	}
	return 0;
}

//Delete latest cache items from data array.
AOM.ImageCache.prototype.deleteLatest = function()
{
	this.data.sort(AOM.ImageCache.sortDateDescending);
	while(this.data.length > 0) {
		if (this.data[0].latestOperation == 0) {
			this.releaseByIndex(0);
		} else {
			break;
		}
	}
}

// Add a cache item to data array.
AOM.ImageCache.prototype.addItem = function(item)
{
	this.data.push(item);
}

// Create a new cache item into the cache array and generate its .jpg image
AOM.ImageCache.prototype.createNewCacheItem = function(thumbnail, demandInfo)
{
	var file = new File(thumbnail.path);
	var item = {};
	
	item.originFilePath = thumbnail.path;
	item.originSize = file.length;
	item.modifiedDate = AOM.ImageCache.getDateString(thumbnail.lastModifiedDate);
	item.lastVisitedTime = AOM.ImageCache.getDateString(new Date());
	item.jpegQuality = demandInfo.jpegQuality;
	
	var fileInfo = this.cacheImage(thumbnail, demandInfo);
	item.diskFull = fileInfo.diskFull;
	if (item.diskFull) {
		item.tempFileName = undefined;
		item.canOpenFile = false;
		return item;
	}
	if (fileInfo.fileName == undefined) {
		item.tempFileName = undefined;
		item.canOpenFile = false;
		this.addItem(item);
		return item;
	}

	item.colorProfilePreserved = fileInfo.colorProfilePreserved;
	item.rotation = fileInfo.rotation;
	item.tempFileName = fileInfo.fileName;
	item.height = fileInfo.height;
	item.width = fileInfo.width;
	item.hasResized = fileInfo.hasResized;
	item.canOpenFile = true;
	item.latestOperation = 0;
	this.addItem(item);
	item.imageBits = fileInfo.imageBits;
	return item;
}


// Delete a cache item from the array by the index
AOM.ImageCache.prototype.releaseByIndex = function(index)
{
	if (this.data[index].canOpenFile)
		this.deleteFile(this.data[index].tempFileName);
	this.data.splice(index, 1);
}

// Delete a cache item from the array by the temp file name
AOM.ImageCache.prototype.releaseByName = function(tempFileName)
{
	var index = -1;
	for (var i = 0; i < this.data.length; i++) {
		if (tempFileName == this.data[i].tempFileName) {
			index = i;
			break;
		}
	}
	
	if (index != -1) {
		this.releaseByIndex(index);
	}
}

// Delete a temporary image file by its name
AOM.ImageCache.prototype.deleteFile = function(temporaryFileName)
{
	file = new File(this.tempImageCacheFolder + File.encode(temporaryFileName) + ".jpg");
	file.remove();
}

// Generate a .jpg file as a cache from original file or bridge cache
AOM.ImageCache.prototype.cacheImage = function(thumbnail, demandInfo)
{
	var imageBits;
	var fromBridgeCache = false;
	
	// Pick a temp image from bridge preview cache if possible
	if(thumbnail.core.preview.cacheData.status != "good") {
		imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
	} else {
		if(demandInfo.colorProfilePreserved) {
			var meta = thumbnail.metadata;
			if(meta == undefined) {
				imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			} else {
				meta.namespace = "http://ns.adobe.com/photoshop/1.0/";
				if(meta.ICCProfile != "sRGB" && meta.ICCProfile != "sRGB IEC61966-2.1")
					imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			}
		}
		
		if(imageBits == undefined) {
			fromBridgeCache = true;
			app.synchronous = true;
			imageBits = thumbnail.core.preview.preview;
			imageBits.needDispose = false;
			
			if(imageBits == undefined || imageBits.width == undefined || imageBits.height == undefined) {
				fromBridgeCache = false;
				imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			} else {
				var w = imageBits.width;
				var h = imageBits.height;
				var dw = demandInfo.width;
				var dh = demandInfo.height;
				var imageTooSmall = false;
				if(w == 1024 || h == 1024) {
					if (demandInfo.fullResolution) {
						imageTooSmall = true;
					} else {
						if (demandInfo.rotateToFit) {
							if((w < dw && h < dh) ||
								(w < dh && h < dw)) {
								imageTooSmall = true;
							}
						} else {
							if ((w < dw && h < dh)) {
									imageTooSmall = true;
							}
						}
					}
				}
				if (imageTooSmall) {
					fromBridgeCache = false;
					if (imageBits.needDispose != false)
						imageBits.dispose();
					imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
				}
			}
			app.synchronous = false;
		}
	}
	
	var fileInfo = {};
	if(imageBits == undefined || imageBits.width == undefined || imageBits.height == undefined) {
		fileInfo.fileName = undefined;
		return fileInfo;
	}
	
	var result = this.calculateDemand(thumbnail, demandInfo, imageBits.height, imageBits.width);

	if (result.rotation != 0) {
		var oldImage = imageBits;
		imageBits = imageBits.rotate(result.rotation);
		if (oldImage.needDispose != false)
			oldImage.dispose();
	}
	
	fileInfo.hasResized = false;
	if(!demandInfo.fullResolution && result.height < imageBits.height) {
		fileInfo.hasResized = true;
		var resizeBound = Math.round(Math.max(result.height, result.width));
		oldImage = imageBits;
		imageBits = imageBits.resize(resizeBound, "bicubicSharper");
		if (oldImage.needDispose != false)
			oldImage.dispose();
	}
	var that = this;
	
	fileInfo.fileName = undefined;
	fileInfo.height = imageBits.height;
	fileInfo.width = imageBits.width;
	fileInfo.colorProfilePreserved = result.colorProfilePreserved;
	fileInfo.rotation = result.rotation;
	fileInfo.diskFull = false;
	
	fileInfo.fileName = this.fileNumber + "";
	this.fileNumber++;
	var targetFile = File(this.tempImageCacheFolder + File.encode(fileInfo.fileName) + ".jpg");
	var name = targetFile.fsName;
	imageBits.exportTo(name, demandInfo.jpegQuality, true);
	fileInfo.imageBits = imageBits;
	if (!targetFile.exists) {
		fileInfo.diskFull = true;
		if (imageBits.needDispose != false)
			imageBits.dispose();
	}
	return fileInfo;
}


AOM.ImageCache.prototype.createBitmapData = function(thumbnail, sizesList)
{
	var maxSize = 0;
	var demandInfo;
	var fromBridgeCache = false;
	var imageBits;
	for(var i in sizesList) {
		demandInfo = thumbnail.amg.sizes[i].demandInfo;
		if(thumbnail.amg.sizes[i].needCreate) {
			max = Math.max(demandInfo.height, demandInfo.width);
			if (max > maxSize)
				maxSize = max;
		}
	}
	
	if(thumbnail.core.preview.cacheData.status != "good") {
		imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
	} else {
		if(demandInfo.colorProfilePreserved) {
			var meta = thumbnail.metadata;
			if(meta == undefined) {
				imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			} else {
				meta.namespace = "http://ns.adobe.com/photoshop/1.0/";
				if(meta.ICCProfile != "sRGB" && meta.ICCProfile != "sRGB IEC61966-2.1")
					imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			}
		}
		
		if(imageBits == undefined) {
			fromBridgeCache = true;
			app.synchronous = true;
			imageBits = thumbnail.core.preview.preview;
			
			if(imageBits == undefined || imageBits.width == undefined || imageBits.height == undefined) {
				fromBridgeCache = false;
				imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
			} else {
				var imageTooSmall = false;
				if(imageBits.width == 1024 || imageBits.height == 1024) {
					if ((imageBits.width < maxSize && imageBits.height < maxSize)) {
							imageTooSmall = true;
					}
				}
				if (imageTooSmall) {
					fromBridgeCache = false;
					oldImage = imageBits;
					imageBits = new BitmapData(thumbnail.path, demandInfo.colorProfilePreserved);
					oldImage.dispose();
				}
			}
			app.synchronous = false;
		}
	}
	if(imageBits == undefined || imageBits.width == undefined || imageBits.height == undefined) {
		return undefined;
	}
	return imageBits;
}



AOM.ImageCache.prototype.createNullItem = function(thumbnail)
{
	var file = new File(thumbnail.path);
	var item = {};
	item.originFilePath = thumbnail.path;
	item.originSize = file.length;
	item.modifiedDate = AOM.ImageCache.getDateString(thumbnail.lastModifiedDate);
	item.lastVisitedTime = AOM.ImageCache.getDateString(new Date());
	item.diskFull = false;
	item.tempFileName = undefined;
	item.canOpenFile = false;
	item.jpegQuality = undefined;
	this.addItem(item);
}



AOM.ImageCache.prototype.createNewCacheItemFromBitmap = function(thumbnail, demandInfo, imageBits, sizesList, jpegQuality)
{
	var file = new File(thumbnail.path);
	var item = {};
	
	item.originFilePath = thumbnail.path;
	item.originSize = file.length;
	item.modifiedDate = AOM.ImageCache.getDateString(thumbnail.lastModifiedDate);
	item.lastVisitedTime = AOM.ImageCache.getDateString(new Date());
	item.hasResized = false;
	item.diskFull = false;
	
	var result = this.calculateDemand(thumbnail, demandInfo, imageBits.height, imageBits.width);
	
	if(result.height < imageBits.height) {
		item.hasResized = true;
		var resizeBound = Math.round(Math.max(result.height, result.width));
		var newImage = imageBits.resize(resizeBound, "bicubicSharper");
	}
	var that = this;
	
	if(newImage != undefined) {
		item.height = newImage.height;
		item.width = newImage.width;
	} else {
		item.height = imageBits.height;
		item.width = imageBits.width;
	}
	item.tempFileName = undefined;
	item.colorProfilePreserved = result.colorProfilePreserved;
	item.rotation = result.rotation;
	item.jpegQuality = jpegQuality;
	
	item.tempFileName = this.fileNumber + "";
	this.fileNumber++;
	var targetFile = File(this.tempImageCacheFolder + File.encode(item.tempFileName) + ".jpg");
	var name = targetFile.fsName;
	if(newImage != undefined) {
		newImage.exportTo(name, jpegQuality, true);
		newImage.dispose();
	} else {
		imageBits.exportTo(name, jpegQuality, true);
	}
	if (!targetFile.exists) {
		item.diskFull = true;
		item.tempFileName = undefined;
		item.canOpenFile = false;
		return item;
	}

	item.canOpenFile = true;
	item.latestOperation = 0;
	this.addItem(item);
	return item;
}
