#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
from maya import cmds
from time import time
import cryDecorators
import sys

import logging
logging.basicConfig()
log = logging.getLogger(__name__)
#log.setLevel(logging.DEBUG)

standardSGs = set(['initialParticleSE', 'initialShadingGroup'])


def renameShadingEngines(lstShadingEngines=None):
    '''
    Renames ShadingEngines, to match their material's name.
    This is required, because maya exports FaceSets from ShadingEngines
    and GeomCaches derives material IDs from FaceSets.
    '''
    
    if not lstShadingEngines:
        lstShadingEngines = cmds.ls(type="shadingEngine")
    elif isinstance(lstShadingEngines, basestring):
        lstShadingEngines = [lstShadingEngines]
    
    # can't rename standard shading engines
    lstShadingEngines = list(set(lstShadingEngines) - standardSGs)
    
    if not lstShadingEngines:
        return []

    log.info("Renaming ShadingEngines...")
    log.debug("  %s" % ', '.join(lstShadingEngines))
    
    for i in range(len(lstShadingEngines)):
        lstMaterials = cmds.listConnections("%s.surfaceShader" % lstShadingEngines[i], d=False, s=True, p=False)
        if lstMaterials:
            log.debug("  Materials: %s" % ', '.join(lstMaterials))
            dgMaterial = lstMaterials[0]
            destName = "%sSG" % dgMaterial
            if lstShadingEngines[i] == destName:
                continue
            
            log.debug("Renaming " + lstShadingEngines[i] + " to " + destName + " ...")
            try:
                lstShadingEngines[i] = cmds.rename(lstShadingEngines[i], destName)
                print('lstShadingEngines[i]: %s' % lstShadingEngines[i])
            except:
                log.debug("  !!! FAILED TO RENAME !!!")
        else:
            log.debug("No material found for shadingEngine: " + lstShadingEngines[i])
    return lstShadingEngines


def enforcePerFaceAssignment(lstShadingEngines=None):
    '''
    Workaround for Maya 2014/2015 bug.
    Ensures that ShadingEngines contain only faces, rather than full shapes.
    Alembic 1.1.5 contains a bug, so that it doesn't export FaceSets
    for objects where the entire shape is in the ShadingEngine.
    '''
    if lstShadingEngines is None:
        lstShadingEngines = cmds.ls(type="shadingEngine")
    else:
        if isinstance(lstShadingEngines, basestring):
            lstShadingEngines = [lstShadingEngines]
    
    # cant apply per face assignment on standardSGs 
    lstShadingEngines = set(lstShadingEngines) - standardSGs
    
    if not lstShadingEngines:
        return False
    
    log.info("Enforcing per-face material assignment ...")
    
    for dgShadingEngine in lstShadingEngines:
        log.debug("  shadingEngine: " + dgShadingEngine)
        
        lstSetMembers = cmds.sets(dgShadingEngine, q=True)
        if not lstSetMembers:
            continue
        
        for sSetMember in lstSetMembers:
            if ".f[" not in sSetMember:
                log.debug("  Converting shape-assignment to face-assignment: " + sSetMember)
                # make sure sSetMember is a mesh!
                meshMember = cmds.ls(cmds.listHistory(sSetMember), type='mesh')
                if not meshMember:
                    log.debug('SetMember "%s" (from %s) did not yield a mesh!' % sSetMember, dgShadingEngine)
                    continue
                meshMember = meshMember[0]
                try:
                    #iFaceCount = cmds.polyEvaluate(meshMember, f=True)
                    #sFaces = meshMember + ".f[0:" + str(iFaceCount - 1) + "]"
                    sFaces = "%s.f[*]" % meshMember
                    # nasty hack: temporarily apply default material
                    # to generate objectGroup datastructures
                    if dgShadingEngine not in standardSGs:
                        cmds.sets("%s.f[0]" % meshMember, e=True, fe="initialShadingGroup")
                    cmds.sets(sFaces, e=1, fe=dgShadingEngine)
                    # remove original member from that we added all faces if still in
                    if cmds.sets(sSetMember, isMember=dgShadingEngine):
                        cmds.sets(sSetMember, rm=dgShadingEngine)
                except StandardError, error:
                    log.error(error)
                    log.debug("Failed to all faces to material for object: " + sSetMember + "!")
                    log.debug(sys.exc_info())

        # fix for abc import which throws per face warnings if you have 
        # get connected visible shapes
        connMesh = set(cmds.ls(cmds.listConnections('%s.dagSetMembers' % dgShadingEngine, shapes=True), type='mesh'))
        connMesh = set([m for m in connMesh if not cmds.getAttr('%s.intermediateObject' % m)])
        shapeMembers = set(cmds.ls(lstSetMembers, s=True, dag=True, o=True))
        # find nodes connected to dagSetMembers but are not members
        nonMemberShapes = list(connMesh - shapeMembers)
        if nonMemberShapes:
            log.debug('ShadingEngine "%s" has false member connection to mesh shape: %s'
                      % (dgShadingEngine, ', '.join(nonMemberShapes)))
            # find out exact connection and disconnect
            for shape in nonMemberShapes:
                conns = cmds.listConnections(shape, type='shadingEngine', c=True, p=True)
                for i in range(1, len(conns), 2):
                    if conns[i].startswith('%s.dagSetMembers' % dgShadingEngine):
                        log.debug('unplugging "%s" from "%s"' % (conns[i - 1], conns[i]))
                        cmds.disconnectAttr(conns[i - 1], conns[i])

    return True

@cryDecorators.pluginDependency("AbcExport.mll")
def exportAlembicForGeomCache(nodes=[]):
    '''
    Exports objects to Alembic with settings required for GeomCache pipeline.
    '''
    if not nodes:
        nodes = cmds.ls(sl=True, tr=True)
    if not nodes:
        log.error('Select some transforms to export!')
        return
    
    selMesh, selSGs = getMeshAndSGs(nodes)
    if not selMesh:
        log.error('No meshes found to export!')

    if selSGs.intersection(standardSGs):
        log.warning('Selected objects have default materials applied! Please replace!')
    
    #lstFilePath = cmds.fileDialog2(cap="Export Alembic for GeomCache", ff="*.abc", ds=1, fm=0, dir=(cmds.workspace(q=1, rd=1)))
    lstFilePath = cmds.fileDialog2(cap="Export Alembic for GeomCache", fileFilter="Alembic .abc (*.abc)", fileMode=0)
    if not lstFilePath:
        return
   
    t0 = time()
    selSGs = renameShadingEngines(selSGs)
    enforcePerFaceAssignment(selSGs)

    sOptions = "-frameRange %s %s" % (cmds.playbackOptions(q=1, min=1), cmds.playbackOptions(q=1, max=1))
    sOptions += " -uvWrite -writeColorSets -writeFaceSets -writeVisibility "
    sOptions += ' '.join(['-root %s' % n for n in nodes])
    sOptions += ' -file "%s"' % lstFilePath[0]
    log.debug("AbcExport params:\n\t%s" % sOptions)
    
    log.info('Exporting file: "%s" ...' % lstFilePath[0])
    try:
        cmds.AbcExport(jobArg=sOptions)
    except StandardError, error:
        log.error(error)
        log.debug(sys.exc_info())
    
    log.info('Export finished! %ssec' % round(time() - t0, 1))


def getMeshAndSGs(nodes=[]):
    """
    filters visible mesh shapes from given or selected nodes and returns
    them with thier shading groups
    """
    if not nodes:
        nodes = cmds.ls(sl=True)
    if not nodes:
        return None, None
    selMesh = set(cmds.ls(cmds.listRelatives(nodes, allDescendents=True), type='mesh'))
    selMesh = [m for m in selMesh if not cmds.getAttr('%s.intermediateObject' % m)]
    if not selMesh:
        return None, None
    selSGs = set(cmds.ls(cmds.listConnections(selMesh), type='shadingEngine'))
    return selMesh, selSGs


def prepareMaterials(*args):
    """inline call for Ui"""
    mesh, sgs = getMeshAndSGs()
    sgs= renameShadingEngines(sgs)
    enforcePerFaceAssignment(sgs)