import os, sys, subprocess
import maya.cmds as cmds
import maya.mel as mel

# for file resolution on update all images
import maya.OpenMaya as OpenMaya
import maya.OpenMayaRender as OpenMayaRender


def refreshMapFile(fileName):
	"""
	Check for a cache file corresponding to the image file denoted by fileName
	and generate it (using imf_copy -p -r) if it is out of date or missing

	Returns the name of the generated file if successful, and an empty string
	otherwise
	"""
	mapFileName = ""
	optimFormat = ("map")

	if os.path.exists(fileName):
		# name of generated file will be the original name plus new extension
		mapFileName = ('%s.%s') % (fileName, optimFormat)
		
		# find and resolve directory to store optim files in, if can't
		# then fall back to storing in same location as originals
		cacheDir = ""
		storageMode = cmds.optionVar(q=('miFileTextureCacheStorageMode'))
		if storageMode == 0:
			cacheDir = __resolveDirectory(('sourceimages/cache'))
		elif storageMode == 1:
			cacheDir = __resolveDirectory(
							cmds.optionVar(q=('miFileTextureCacheLocation')))
		if cacheDir and os.path.isdir(cacheDir):
			mapFileName = ('%s/%s') % (cacheDir,os.path.basename(mapFileName))

		# perform conversion if necessary
		if ((not os.path.exists(mapFileName)) or
				os.path.getmtime(mapFileName) < os.path.getmtime(fileName)):
			message = mel.eval('getPluginResource("Mayatomr", "kConvertingImageFile")')
			message %= fileName
			__miInfo(message)

			# create imf_copy command
			cmdAndArgs = [ ('%s/bin/imf_copy') %
							mel.eval(('getenv("MENTALRAY_LOCATION")')),
							('-p'), ('-r'), fileName, mapFileName, optimFormat ]

			# special case for HDR images to use proper format for caching
			if fileName.lower().endswith(('.hdr')):
				cmdAndArgs.append(('rgb_fp'))

			# prevent shell window from appearing on windows
			startupinfo = None
			if cmds.about(win=True):
				startupinfo = subprocess.STARTUPINFO()
				startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW

			# encode params to subprocess in the system encoding because 
			# the subprocess module does not play nice with unicode strings
			fse = sys.getfilesystemencoding()
			try:
				cmdAndArgs = [ arg.encode(fse) if isinstance(arg, unicode) else arg for arg in cmdAndArgs ]
			except:
				message = mel.eval('getPluginResource("Mayatomr", "kmrRenderlayerPresMenuEncodingError")')
				message %= ' '.join(cmdAndArgs)
				__miWarn(message)

			# run command through system
			try:
				result = subprocess.call(cmdAndArgs, startupinfo=startupinfo)
			except:
				message = mel.eval('getPluginResource("Mayatomr", "kmrRenderlayerPresMenuRunProcessFailed")')
				message %= { ('filename') : fileName, 
								('command') : ' '.join(cmdAndArgs) }
				__miWarn(message)
				mapFileName = ""
			else:
				# imf_copy returns 0 on success
				if result:
					message = mel.eval('getPluginResource("Mayatomr", "kConversionFailed")')
					message %= { ('code') : result, ('filename') : fileName }
					__miWarn(message)
					mapFileName = ""
				else:
					message = mel.eval('getPluginResource("Mayatomr", "kConversionSucceeded")')
					message %= mapFileName
					__miInfo(message)
	else:
		message = mel.eval('getPluginResource("Mayatomr", "kmrRenderlayerPresMenuImageFileMissing")')
		message %= fileName 
		__miWarn(message)

	# return name of the cache file
	return mapFileName


def refreshAllMapFiles(referencedOnly=True):
	"""
	Refresh (where required) the cache files corresponding to file
	textures in the scene. Currently the MR plugin assumes those to be
	nodes of type "file", "mentalrayTexture" or "psdFileTex"
	
	If referencedOnly is True, only convert files that appear to be used in
	the scene. Otherwise convert all files.
	"""

	# build list of potential files
	textureList = []
	nodes = cmds.ls(exactType=('file'))
	if nodes:
		textureList += [ (n, ('file')) for n in nodes ]

	nodes = cmds.ls(exactType=('mentalrayTexture'))
	if nodes:
		textureList += [ (n, ('mentalrayTexture')) for n in nodes ]

	nodes = cmds.ls(exactType=('psdFileTex'))
	if nodes:
		textureList += [ (n, ('psdFileTex')) for n in nodes ]

	if not textureList:
		message = mel.eval('getPluginResource("Mayatomr", "kNoTexturesPresent")')
		__miWarn(message)
		return

	# process list of file nodes, discarding any unreferenced ones (if
	# requested), extract file name from each node
	fileList = set() # use set to eliminate duplicates
	numTested = 0
	numTextures = len(textureList)
	message = mel.eval('getPluginResource("Mayatomr", "kExaminingTextures")')
	interruptedMessage = mel.eval('getPluginResource("Mayatomr", "kInterrupted")')
	__startProgress(numTextures, message % numTextures)
	for (texture, nodeType) in textureList:
		if __interrupted():
			__miWarn(interruptedMessage)
			break

		if referencedOnly and not __textureNodeUsed(texture):
			message = mel.eval('getPluginResource("Mayatomr", "kFileNotRef")')
			message %= cmds.getAttr(('%s.fileTextureName') % texture)
			__miInfo(message)
		else:
			fileName = ""
			# mental ray doesn't support PSD files directly, convert to
			# IFF first and use IFF name instead
			if (nodeType == ('psdFileTex')):
				fileName = mel.eval(
							('doPsdToIffConversion("%s", 0)') % texture)
			else:
				fileName = __resolveFileName(texture)
			if fileName:
				fileList.add(fileName)

		numTested += 1
		__updateProgress(numTested)
	__endProgress()
	
	if not fileList:
		message = mel.eval('getPluginResource("Mayatomr", "kNoRefTextures")')
		__miWarn(message)
		return

	if numTested < numTextures:
		return # user interrupt, don't do convert

	# perform conversions updating progress bar and checking for
	# interrupts at each file
	numConverted = 0
	numFiles = len(fileList)
	message = mel.eval('getPluginResource("Mayatomr", "kConvertingFiles")')
	__startProgress(numFiles, message % numFiles)
	for (fileName, i) in zip(fileList, range(1, numFiles+1)):
		if __interrupted():
			__miWarn(interruptedMessage)
			break
			
		if (refreshMapFile(fileName)):
			numConverted += 1
		__updateProgress(i)
	__endProgress()

	message = mel.eval('getPluginResource("Mayatomr", "kFinalStatus")')
	if numConverted == 1:
		message %= { ('converted') : numConverted,
						('total') : numFiles,
						('plural') : mel.eval('getPluginResource("Mayatomr", "kFile")') }
	else:
		message %= { ('converted') : numConverted,
						('total') : numFiles,
						('plural') : mel.eval('getPluginResource("Mayatomr", "kFiles")') }
	__miInfo(message)


def __resolveDirectory(baseName):
	"""
	Resolve a directory into a complete path. Possibly relative to
	project directory, possibly containing environment variables,
	possibly containing the token <Scene>
	
	Possibly need to create the directory as well
	"""
	result = baseName
	if result:
		sceneName = cmds.file(q=True, shortName=True, sceneName=True)
		if not sceneName:
			sceneName = mel.eval(('untitledFileName()'))
		sceneName = os.path.splitext(sceneName)[0]
		result = result.replace(('<Scene>'), sceneName)
		result = result.replace(('<scene>'), sceneName)
		result = cmds.file(result, q=True, expandName=True)
		if result and not os.path.isdir(result):
			cmds.sysFile(result, makeDir=True)

	return result


def __resolveFileName(texture):
	"""
	Retrieve and resolve the file name associated with a texture node
	"""
	fileName = cmds.getAttr(('%s.fileTextureName') % texture)

	if fileName:
		# Expand path name, including environment variables
		# and project stuff. This uses the API to call the
		# same resolution methods that Maya uses internally
		resolved = False
		try:
			# Create a selection list to get the MObject for the file node
			# given the name; note this does not modify the current selection
			mSelList = OpenMaya.MSelectionList()
			OpenMaya.MGlobal.getSelectionListByName(texture, mSelList)
			mNode = OpenMaya.MObject()
			for i in range(mSelList.length()):
				# Assume first matching file node is correct.
				# This is safe because you cannot have multiple file
				# nodes with the same name (even with namespaces, the
				# name comes out as namespace:name)
				mSelList.getDependNode(i, mNode)
				if (mNode.hasFn(OpenMaya.MFn.kFileTexture) or 
						mNode.hasFn(OpenMaya.MFn.kPsdFileTexture)):
					fileName = OpenMayaRender.MRenderUtil.exactFileTextureName(mNode)
					resolved = True
					break
		except:
			# fall through on error
			pass

		if not resolved:
			# fall back to command based file name expansion
			fileName = cmds.file(fileName, query=True, expandName=True)

	return fileName


def __textureNodeUsed(node):
	"""
	Check if the file texture node is part of any active shading network (ie.
	one	that has objects in it.
	
	Method may return some false positives, but shouldn't miss any
	true positives.
	
	Supports both maya and mental ray file texture nodes.
	"""
	downstreamNodes = cmds.hyperShade(listDownstreamNodes=('%s') % node)

	if downstreamNodes is not None:
		for dn in downstreamNodes:
			if (cmds.nodeType(dn) == ('shadingEngine') and
					not mel.eval(('shadingGroupUnused("%s")') % dn)):
				return True

	return False


# Output functions
def __miInfo(message):
	mel.eval((u'miInfo("%s")') % message)


def __miWarn(message):
	mel.eval((u'miWarn("%s")') % message)
	

# functions and data for using interruptible progress monitoring in convert
# all textures function
__mainProgressBar = mel.eval(
		('global string $gMainProgressBar; $gMainProgressBar = $gMainProgressBar'))

def __startProgress(maxVal, message):
	cmds.progressBar(__mainProgressBar, edit=True, beginProgress=True)
	cmds.progressBar(__mainProgressBar, edit=True,
						isInterruptable=True,
						minValue=1,
						maxValue=maxVal > 1 and maxVal or 2,
						status=message)


def __interrupted():
	return cmds.progressBar(__mainProgressBar, query=True, isCancelled=True)


def __updateProgress(val):
	cmds.progressBar(__mainProgressBar, edit=True, progress=val)


def __endProgress():
	cmds.progressBar(__mainProgressBar, edit=True, endProgress=True)

# Copyright (C) 1997-2014 Autodesk, Inc., and/or its licensors.
# All rights reserved.
#
# The coded instructions, statements, computer programs, and/or related
# material (collectively the "Data") in these files contain unpublished
# information proprietary to Autodesk, Inc. ("Autodesk") and/or its licensors,
# which is protected by U.S. and Canadian federal copyright law and by
# international treaties.
#
# The Data is provided for use exclusively by You. You have the right to use,
# modify, and incorporate this Data into other products for purposes authorized 
# by the Autodesk software license agreement, without fee.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. AUTODESK
# DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTIES
# INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF NON-INFRINGEMENT,
# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, OR ARISING FROM A COURSE 
# OF DEALING, USAGE, OR TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS
# LICENSORS BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
# DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK AND/OR ITS
# LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY OR PROBABILITY OF SUCH DAMAGES.

