import io, math, os, re

import pluginUtils as pU
log = pU.log.getLogger('V3D-MY')

import mayaUtils

import maya.cmds as cmds

import pcpp, pyosl.oslparse, pyosl.glslgen

RAMP_DATA_SIZE = 512

SUPPORTED_NODES = {
    'addDoubleLinear': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'output'
        ]
    },
    'addMatrix': {
         # initialized on demand
        'in': [],
        'out': [
            'matrixSum'
        ]
    },
    'aiAbs': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiAdd': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiAtan': {
        'in': [
            'x',
            'y'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiCheckerboard': {
        'in': [
            'color1',
            'color2',
            'uFrequency',
            'vFrequency',
            'uOffset',
            'vOffset',
            'contrast'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiClamp': {
        'in': [
            'input',
            'min',
            'max',
            'minColor',
            'maxColor'
        ],
        'out': [
            'outColor',
            'outTransparency'
        ]
    },
    'aiColorConvert': {
        'in': [
            'input'
        ],
        'out': [
            'outColor',
            'outTransparency'
        ]
    },
    'aiColorCorrect': {
        'in': [
            'input',
            'mask',
            'gamma',
            'hueShift',
            'saturation',
            'contrast',
            'contrastPivot',
            'exposure',
            'multiply',
            'add',
            'invert',
            'alphaIsLuminance',
            'alphaMultiply',
            'alphaAdd',
            'invertAlpha'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'aiColorToFloat': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiCompare': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiComplement': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiComposite': {
        'in': [
            'A',
            'B',
            'AA',
            'BA'
        ],
        'out': [
            'outAlpha',
            'outColor',
            'outTransparency'
        ]
    },
    'aiCross': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiDivide': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiDot': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiExp': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiFacingRatio': {
        'in': [
            'bias',
            'gain',
            'invert',
            'linear'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiFlat': {
        'in': [
            'color'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiFloatToInt': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiFloatToMatrix': {
        'in': [
            'input00',
            'input01',
            'input02',
            'input03',
            'input10',
            'input11',
            'input12',
            'input13',
            'input20',
            'input21',
            'input22',
            'input23',
            'input30',
            'input31',
            'input32',
            'input33'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiFloatToRgba': {
        'in': [
            'r',
            'g',
            'b',
            'a'
        ],
        'out': [
            'outColor',
            'outAlpha',
            'outTransparency'
        ]
    },
    'aiFraction': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiIsFinite': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiImage': {
        'in': [
            'multiply',
            'offset',
            'uvcoords',
            'soffset',
            'toffset',
            'sscale',
            'tscale',
            'sflip',
            'tflip',
            'swapSt'
        ],
        'out': [
            'outColor',
            'outAlpha',
            'outTransparency'
        ]
    },
    'aiLambert': {
        'in': [
            'Kd',
            'KdColor',
            'normalCamera',
            'opacity'
        ],
        'out': [
            'outColor', # place it first for compatibility with Max version
            'outAlpha',
            'outTransparency'
        ]
    },
    'aiLength': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiLog': {
        'in': [
            'input',
            'base'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiMatrixTransform': {
        'in': [
            'rotation',
            'axis',
            'angle',
            'translate',
            'scale',
            'pivot'
        ],
        'out': [
            'outValue',
            'outTransparency'
        ]
    },
    'aiMax': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiMin': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiMixShader': {
        'in': [
            'mix',
            'shader1',
            'shader2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiModulo': {
        'in': [
            'input',
            'divisor'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiMultiply': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiNegate': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiNormalize': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiNormalMap': {
        'in': [
            'input',
            'normal',
            'strength',
            'tangent'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiOslShader': {
         # initialized on demand
        'in': [],
        'out': [
            # only single output supported in Maya
            'outValue'
        ]
    },
    'aiPow': {
        'in': [
            'base',
            'exponent'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiRandom': {
        'in': [
            'inputColor'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiRange': {
        'in': [
            'input',
            'inputMin',
            'inputMax',
            'outputMin',
            'outputMax',
            'smoothstep',
            'contrast',
            'contrastPivot',
            'bias',
            'gain'
        ],
        'out': [
            'outColor',
            'outTransparency'
        ]
    },
    'aiRaySwitch': {
        'in': [
            'camera',
            'diffuseReflection',
            'diffuseTransmission',
            'shadow',
            'specularReflection',
            'specularTransmission'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'aiReciprocal': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiRgbaToFloat': {
        'in': [
            'input',
            'inputA'
        ],
        'out': [
            'outValue',
            'outTransparency'
        ]
    },
    'aiRgbToVector': {
        'in': [
            'input'
        ],
        'out': [
            'outValue'
        ]
    },
    'aiShadowMatte': {
        'in': [
            'shadowColor',
            'backlighting',
            'shadowOpacity'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'aiShuffle': {
        'in': [
            'color',
            'alpha',
            'negateR',
            'negateG',
            'negateB',
            'negateA'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'aiSign': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiSkyDomeLight': {
        'in': [
            'color',
            'intensity'
        ],
        'out': []
    },
    'aiSqrt': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiStandardSurface': {
        'in': [
            'base',
            'baseColor',
            'diffuseRoughness',
            'metalness',
            'specular',
            'specularColor',
            'specularRoughness',
            'transmission',
            'transmissionColor',
            'transmissionDepth',
            'transmissionScatter',
            'transmissionExtraRoughness',
            'subsurface',
            'subsurfaceColor',
            'subsurfaceRadius',
            'coat',
            'coatColor',
            'coatRoughness',
            'sheen',
            'sheenColor',
            'sheenRoughness',
            'emission',
            'emissionColor',
            'opacity',
            'normalCamera',
            'coatNormal',
            'specularIOR',
        ],
        'out' : [
            'outColor',
            'outTransparency'
        ]
    },
    'aiSubtract': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiTrigo': {
        'in': [
            'input',
            'frequency',
            'phase'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiTriplanar': {
        'in': [
            'input',
            'inputY',
            'inputZ',
            'scale',
            'rotate',
            'offset'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiTwoSided': {
        'in': [
            'front',
            'back'
        ],
        'out': [
            'outColor'
        ]
    },
    'aiUserDataColor': {
        'in': [],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'aiUvProjection': {
        'in': [
            'projectionColor',
            'uAngle',
            'vAngle',
            'placementMatrix'
        ],
        'out': [
            'outColor',
            # 'outTransparency' - not supported in V3D
        ]
    },
    'aiVectorToRgb': {
        'in': [
            'input'
        ],
        'out': [
            'outColor'
        ]
    },
    'animCurveTA': {
        'in': [],
        'out': [
            'output'
        ]
    },
    'animCurveTU': {
        'in': [],
        'out': [
            'output'
        ]
    },
    'blendColors': {
        'in': [
            'blender',
            'color1',
            'color2'
        ],
        'out': [
            'output'
        ]
    },
    'blinn': {
        'in': [
            'color',
            'ambientColor',
            'incandescence',
            'matteOpacity',
            'diffuse',
            'normalCamera',
            'reflectivity',
            'reflectedColor',
            'eccentricity',
            'specularColor',
            'specularRollOff',
            'translucence',
            'transparency'
        ],
        'out': [
            'outColor'
        ]
    },
    'bulge': {
        'in': [
            'uWidth',
            'vWidth',
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'bump2d': {
        'in': [
            'bumpDepth',
            'bumpValue'
        ],
        'out': [
            'outNormal'
        ]
    },
    'channels': {
        'in': [
            'inAlpha',
            'inColor'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'checker': {
        'in': [
            'color1',
            'color2',
            'contrast',
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'clamp': {
        'in': [
            'input',
            'max',
            'min'
        ],
        'out': [
            'output'
        ]
    },
    'cloth': {
        'in': [
            'brightSpread',
            'gapColor',
            'randomness',
            'uColor',
            'uWave',
            'uWidth',
            'vColor',
            'vWave',
            'vWidth',
            'widthSpread',
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'colorComposite': {
        'in': [
            'colorA',
            'alphaA',
            'colorB',
            'alphaB',
            'factor'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'colorCondition': {
        'in': [
            'alphaA',
            'alphaB',
            'colorA',
            'colorB',
            'condition'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'colorConstant': {
        'in': [
            'inColor',
            'inAlpha'
        ],
        'out': [
            'outColor',
            'outAlpha'
        ]
    },
    'colorCorrect': {
        'in': [
            'alphaGain',
            'alphaGamma',
            'alphaOffset',
            'colGain',
            'colGamma',
            'colOffset',
            'hueShift',
            'inAlpha',
            'inColor',
            'satGain',
            'valGain'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'colorLogic': {
        'in': [
            'colorA',
            'colorB'
        ],
        'out': [
            'outBool'
        ]
    },
    'colorMask': {
        'in': [
            'inAlpha',
            'inColor',
            'mask',
            'maskAlpha'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'colorMath': {
        'in': [
            'alphaA',
            'alphaB',
            'colorA',
            'colorB'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'composeMatrix': {
        'in': [
            'inputQuat',
            'inputRotate',
            'inputScale',
            'inputShear',
            'inputTranslate'
        ],
        'out': [
            'outputMatrix'
        ]
    },
    'condition': {
        'in': [
            'colorIfFalse',
            'colorIfTrue',
            'firstTerm',
            'secondTerm'
        ],
        'out': [
            'outColor'
        ]
    },
    'contrast': {
        'in': [
            'value',
            'contrast',
            'bias'
        ],
        'out': [
            'outValue'
        ]
    },
    'decomposeMatrix': {
        'in': [
            'inputMatrix'
        ],
        'out': [
            'outputQuat',
            'outputRotate',
            'outputScale',
            'outputShear',
            'outputTranslate'
        ]
    },
    'envSphere': {
        'in': [
            'image',
            'placementMatrix'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'file': {
        'in': [
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor',
            'outSize',
            'outTransparency'
        ]
    },
    'floatComposite': {
        'in': [
            'floatA',
            'floatB',
            'factor'
        ],
        'out': [
            'outFloat'
        ]
    },
    'floatCondition': {
        'in': [
            'condition',
            'floatA',
            'floatB',
        ],
        'out': [
            'outFloat'
        ]
    },
    'floatConstant': {
        'in': [
            'inFloat'
        ],
        'out': [
            'outFloat'
        ]
    },
    'floatLogic': {
        'in': [
            'floatA',
            'floatB'
        ],
        'out': [
            'outBool'
        ]
    },
    'floatMask': {
        'in': [
            'inFloat',
            'mask'
        ],
        'out': [
            'outFloat'
        ]
    },
    'floatMath': {
        'in': [
            'floatA',
            'floatB'
        ],
        'out': [
            'outFloat'
        ]
    },
    'fourByFourMatrix': {
        'in': [
            'in00',
            'in01',
            'in02',
            'in03',
            'in10',
            'in11',
            'in12',
            'in13',
            'in20',
            'in21',
            'in22',
            'in23',
            'in30',
            'in31',
            'in32',
            'in33'
        ],
        'out': [
            'output'
        ]
    },
    'gammaCorrect': {
        'in': [
            'gamma',
            'value'
        ],
        'out': [
            'outValue'
        ]
    },
    'grid': {
        'in': [
            'contrast',
            'fillerColor',
            'lineColor',
            'uWidth',
            'vWidth',
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'inverseMatrix': {
        'in': [
            'inputMatrix'
        ],
        'out': [
            'outputMatrix'
        ]
    },
    'lambert': {
        'in': [
            'color',
            'ambientColor',
            'incandescence',
            'matteOpacity',
            'diffuse',
            'normalCamera',
            'translucence',
            'transparency'
        ],
        'out': [
            'outColor'
        ]
    },
    'layeredTexture': {
         # initialized on demand
        'in': [],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'luminance': {
        'in': [
            'value'
        ],
        'out': [
            'outValue'
        ]
    },
    'multiplyDivide': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'output'
        ]
    },
    'multDoubleLinear': {
        'in': [
            'input1',
            'input2'
        ],
        'out': [
            'output'
        ]
    },
    'multMatrix': {
         # initialized on demand
        'in': [],
        'out': [
            'matrixSum'
        ]
    },
    'noise': {
        'in': [
            'amplitude',
            'depthMax',
            'frequency',
            'frequencyRatio',
            'implode',
            'implodeCenter',
            'inflection',
            'ratio',
            'threshold',
            'time',
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'openPBRSurface': {
        'in': [
            'baseWeight',
            'baseColor',
            'baseDiffuseRoughness',
            'baseMetalness',
            'specularWeight',
            'specularColor',
            'specularRoughness',
            'specularIOR',
            'transmissionWeight',
            'transmissionColor',
            'transmissionDepth',
            'transmissionScatter',
            'fuzzWeight',
            'fuzzColor',
            'fuzzRoughness',
            'coatWeight',
            'coatColor',
            'coatRoughness',
            'emissionLuminance',
            'emissionColor',
            'geometryOpacity',
            'normalCamera'
        ],
        'out' : [
            'outColor'
        ]
    },
    'phong': {
        'in': [
            'color',
            'ambientColor',
            'incandescence',
            'matteOpacity',
            'diffuse',
            'normalCamera',
            'reflectivity',
            'reflectedColor',
            'cosinePower',
            'specularColor',
            'translucence',
            'transparency'
        ],
        'out': [
            'outColor'
        ]
    },
    'phongE': {
        'in': [
            'color',
            'ambientColor',
            'incandescence',
            'matteOpacity',
            'diffuse',
            'normalCamera',
            'reflectivity',
            'reflectedColor',
            'highlightSize',
            'roughness',
            'specularColor',
            'translucence',
            'transparency'
        ],
        'out': [
            'outColor'
        ]
    },
    'place2dTexture': {
        'in': [
            'offset',
            'repeatUV',
            'rotateUV'
        ],
        'out': [
            'outUV'
        ]
    },
    'premultiply': {
        'in': [
            'inAlpha',
            'inColor'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'ramp': {
        'in': [
            'uvCoord'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'remapColor': {
        'in': [
            'color',
            'inputMin',
            'inputMax',
            'outputMin',
            'outputMax'
        ],
        'out': [
            'outColor'
        ]
    },
    'remapHsv': {
        'in': [
            'color',
            'inputMin',
            'inputMax',
            'outputMin',
            'outputMax'
        ],
        'out': [
            'outColor'
        ]
    },
    'remapValue': {
        'in': [
            'inputValue',
            'inputMin',
            'inputMax',
            'outputMin',
            'outputMax'
        ],
        'out': [
            'outValue'
        ]
    },
    'reverse': {
        'in': [
            'input'
        ],
        'out': [
            'output'
        ]
    },
    'samplerInfo': {
        'in': [
            'normalCamera'
        ],
        'out': [
            'facingRatio',
            'flippedNormal',
            'matrixEyeToWorld',
            'normalCamera',
            'pixelCenter',
            'pointCamera',
            'pointObj',
            'pointWorld',
            'tangentUCamera',
            'tangentVCamera',
            'uvCoord'
        ]
    },
    'setRange': {
        'in': [
            'max',
            'min',
            'oldMax',
            'oldMin',
            'value',

        ],
        'out': [
            'outValue',
        ]
    },
    'shadingEngine': {
        'in': [
            'surfaceShader',
            'volumeShader',
            'displacementShader'
        ],
        'out': []
    },
    'surfaceShader': {
        'in': [
            'outColor',
            'outGlowColor',
            'outMatteOpacity',
            'outTransparency'
        ],
        'out': [
            'outColor',
            'outGlowColor',
            'outMatteOpacity',
            'outTransparency'
        ]
    },
    'transform': {
        'in': [],
        'out': [
            'center',
            'inverseMatrix',
            'matrix',
            'parentInverseMatrix',
            'parentMatrix',
            'worldInverseMatrix',
            'worldMatrix',
            'xformMatrix'
        ]
    },
    'transposeMatrix': {
        'in': [
            'inputMatrix'
        ],
        'out': [
            'outputMatrix'
        ]
    },
    'unitConversion': {
        'in': [
            'input'
        ],
        'out': [
            'output'
        ]
    },
    'unpremultiply': {
        'in': [
            'inAlpha',
            'inColor'
        ],
        'out': [
            'outAlpha',
            'outColor'
        ]
    },
    'vectorProduct': {
        'in': [
            'input1',
            'input2',
            'matrix'
        ],
        'out': [
            'output'
        ]
    },
    'wtAddMatrix': {
         # initialized on demand
        'in': [],
        'out': [
            'matrixSum'
        ]
    }
}

SUPPORTED_NODES['standardSurface'] = SUPPORTED_NODES['aiStandardSurface']


def nodeGraphSG(mMat):
    """Find material root aka output aka shading engine aka shading group for the given material"""

    shadingGrps = cmds.listConnections(mMat, type='shadingEngine')
    if shadingGrps:
        # does not matter which one
        return shadingGrps[0]
    else:
        log.error('Material shading group not found: ' + mMat)
        return None


def extractNodeGraph(mMat, gltf, shape=None):

    nodes = []
    edges = []

    processNode(mMat, gltf, nodes, edges, shape)

    return { 'nodes' : nodes, 'edges' : edges }


# NOTE: some nodes like "constant" may calculate Infinity values in outputs which results in failed JSON parsing
def verifyNodeInOuts(inOuts):
    for i in range(0, len(inOuts)):
        out = inOuts[i]
        if isinstance(out, (list, tuple)):
            for j in range(0, len(out)):
                val = inOuts[i][j]
                if not math.isfinite(val):
                    inOuts[i][j] = math.copysign(1e9, val)
        elif isinstance(out, (int, float)):
            if not math.isfinite(out):
                inOuts[i] = math.copysign(1e9, out)

def processNode(mNode, gltf, nodes, edges, shape=None):

    node = {}

    node['name'] = mayaUtils.getName(mNode)
    node['id'] = mNode

    nodeType = cmds.objectType(mNode)

    if nodeType in SUPPORTED_NODES:

        inAttrs = getSupportedInputs(mNode)
        outAttrs = getSupportedOutputs(mNode)

        node['inputs'] = genNodeInOuts(mNode, inAttrs)
        node['outputs'] = genNodeInOuts(mNode, outAttrs)


    if nodeType == 'shadingEngine':
        node['type'] = 'SHADING_ENGINE_MY'
        node['is_active_output'] = True

        # add alpha channel
        node['inputs'][0].append(1)
        # to vec4
        node['inputs'][1] = [0, 0, 0, 0]
        # to vec3
        node['inputs'][2] = [0, 0, 0]

    elif nodeType == 'addMatrix':
        node['type'] = 'ADD_MATRIX_MY'

    elif nodeType == 'aiAbs':
        node['type'] = 'ABS_AR'

    elif nodeType == 'aiAdd':
        node['type'] = 'ADD_AR'

    elif nodeType == 'aiAtan':
        node['type'] = 'ATAN_AR'
        node['units'] = cmds.getAttr(mNode + '.units')

    elif nodeType == 'aiCheckerboard':
        node['type'] = 'CHECKERBOARD_AR'
        node['uvIndex'] = 0

    elif nodeType == 'aiClamp':
        node['type'] = 'CLAMP_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

    elif nodeType == 'aiColorConvert':
        node['type'] = 'COLOR_CONVERT_AR'
        node['from'] = cmds.getAttr(mNode + '.from')
        node['to'] = cmds.getAttr(mNode + '.to')

    elif nodeType == 'aiColorCorrect':
        node['type'] = 'COLOR_CORRECT_AR'

    elif nodeType == 'aiColorToFloat':
        node['type'] = 'COLOR_TO_FLOAT_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')


    elif nodeType == 'aiCompare':
        node['type'] = 'COMPARE_AR'
        node['test'] = cmds.getAttr(mNode + '.test')

    elif nodeType == 'aiComplement':
        node['type'] = 'COMPLEMENT_AR'

    elif nodeType == 'aiComposite':
        node['type'] = 'COMPOSITE_AR'
        node['operation'] = cmds.getAttr(mNode + '.operation')
        node['alphaOperation'] = cmds.getAttr(mNode + '.alphaOperation')

    elif nodeType == 'aiCross':
        node['type'] = 'CROSS_AR'

    elif nodeType == 'aiDivide':
        node['type'] = 'DIVIDE_AR'

    elif nodeType == 'aiDot':
        node['type'] = 'DOT_AR'

    elif nodeType == 'aiExp':
        node['type'] = 'EXP_AR'

    elif nodeType == 'aiFacingRatio':
        node['type'] = 'FACING_RATIO_AR'

    elif nodeType == 'aiFlat':
        node['type'] = 'FLAT_AR'

    elif nodeType == 'aiFloatToInt':
        node['type'] = 'FLOAT_TO_INT_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')
        node['outputTypes'] = ['int']

    elif nodeType == 'aiFloatToMatrix':
        node['type'] = 'FLOAT_TO_MATRIX_AR'

    elif nodeType == 'aiFloatToRgba':
        node['type'] = 'FLOAT_TO_RGBA_AR'
        node['outputs'][0].append(0)

    elif nodeType == 'aiFraction':
        node['type'] = 'FRACTION_AR'

    elif nodeType == 'aiIsFinite':
        node['type'] = 'IS_FINITE_AR'

    elif nodeType == 'aiImage':
        node['type'] = 'IMAGE_AR'
        node['texture'] = pU.gltf.getTextureIndex(gltf, mNode)

    elif nodeType == 'aiLambert':
        node['type'] = 'LAMBERT_AR'
        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'aiLength':
        node['type'] = 'LENGTH_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

    elif nodeType == 'aiLog':
        node['type'] = 'LOG_AR'

    elif nodeType == 'aiMatrixTransform':
        node['type'] = 'MATRIX_TRANSFORM_AR'

        node['transformOrder'] = cmds.getAttr(mNode + '.transformOrder')
        node['rotationType'] = cmds.getAttr(mNode + '.rotationType')
        node['rotationUnits'] = cmds.getAttr(mNode + '.units')
        node['rotationOrder'] = cmds.getAttr(mNode + '.rotationOrder')

    elif nodeType == 'aiMax':
        node['type'] = 'MAX_AR'

    elif nodeType == 'aiMin':
        node['type'] = 'MIN_AR'

    elif nodeType == 'aiMixShader':
        node['type'] = 'MIX_SHADER_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

        # add alpha channel
        node['inputs'][1].append(1)
        node['inputs'][2].append(1)
        node['outputs'][0].append(0)

    elif nodeType == 'aiModulo':
        node['type'] = 'MODULO_AR'

    elif nodeType == 'aiMultiply':
        node['type'] = 'MULTIPLY_AR'

    elif nodeType == 'aiNegate':
        node['type'] = 'NEGATE_AR'

    elif nodeType == 'aiNormalize':
        node['type'] = 'NORMALIZE_AR'

    elif nodeType == 'aiNormalMap':
        node['type'] = 'NORMAL_MAP_AR'

    elif nodeType == 'aiOslShader':
        node['type'] = 'OSL_NODE'

        oslCode = preprocessOSL(cmds.getAttr(mNode + '.code'))
        oslAST = pyosl.oslparse.get_ast(oslCode)

        oslShaderName = 'node_osl_' + oslAST.get_shader_name().lower()
        oslInputs, oslOutputs = parseOSLInOuts(oslAST, oslShaderName)

        node['shaderName'] = oslShaderName
        node['globalVariables'] = [varName for _, varName in pyosl.glslgen.find_global_variables(oslAST)]

        inputTypes = []
        initializers = []

        for i in range(len(oslInputs)):
            inputTypes.append(oslInputs[i][0])

            if oslInputs[i][3]:
                initializers.append([oslInputs[i][3], oslInputs[i][4]])
            else:
                initializers.append(None)

        node['initializers'] = initializers
        node['inputTypes'] = inputTypes

        # override outputs
        node['outputs'] = []
        node['outputTypes'] = []

        for o in oslOutputs:
            node['outputs'].append(o[2])
            node['outputTypes'].append(o[0])

        node['fragCode'] = genOSLCode(oslAST, oslShaderName)

    elif nodeType == 'aiPow':
        node['type'] = 'POW_AR'

    elif nodeType == 'aiRandom':
        node['type'] = 'RANDOM_AR'

    elif nodeType == 'aiRange':
        node['type'] = 'RANGE_AR'

    elif nodeType == 'aiRaySwitch':
        node['type'] = 'RAY_SWITCH_AR'

    elif nodeType == 'aiReciprocal':
        node['type'] = 'RECIPROCAL_AR'

    elif nodeType == 'aiRgbaToFloat':
        node['type'] = 'RGBA_TO_FLOAT_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

    elif nodeType == 'aiRgbToVector':
        node['type'] = 'RGB_TO_VECTOR_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

    elif nodeType == 'aiShadowMatte':
        node['type'] = 'SHADOW_MATTE_AR'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'aiShuffle':
        node['type'] = 'SHUFFLE_AR'

        node['channelR'] = cmds.getAttr(mNode + '.channelR')
        node['channelG'] = cmds.getAttr(mNode + '.channelG')
        node['channelB'] = cmds.getAttr(mNode + '.channelB')
        node['channelA'] = cmds.getAttr(mNode + '.channelA')
        #node['inputTypes'] = ['vec3', 'float', 'bool', 'bool', 'bool', 'bool']

    elif nodeType == 'aiSign':
        node['type'] = 'SIGN_AR'

    elif nodeType == 'aiSkyDomeLight':
        node['type'] = 'SKYDOME_LIGHT_AR'
        node['is_active_output'] = True

    elif nodeType == 'aiSqrt':
        node['type'] = 'SQRT_AR'

    # NOTE: separate types if these two nodes diverge
    elif nodeType == 'aiStandardSurface' or nodeType == 'standardSurface':
        node['type'] = 'STANDARD_SURFACE_AR'
        node['thinWalled'] = cmds.getAttr(mNode + '.thinWalled')

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'aiSubtract':
        node['type'] = 'SUBTRACT_AR'

    elif nodeType == 'aiTrigo':
        node['type'] = 'TRIGO_AR'
        node['function'] = cmds.getAttr(mNode + '.function')
        node['units'] = cmds.getAttr(mNode + '.units')

    elif nodeType == 'aiTriplanar':
        node['type'] = 'TRIPLANAR_AR'
        node['coordSpace'] = cmds.getAttr(mNode + '.coordSpace')
        node['inputPerAxis'] = cmds.getAttr(mNode + '.inputPerAxis')
        node['flipOnOppositeDirection'] = cmds.getAttr(mNode + '.flipOnOppositeDirection')
        node['blendFactor'] = cmds.getAttr(mNode + '.blend')
        node['coordSystem'] = 'Y_UP_RIGHT' # looks like hack

    elif nodeType == 'aiTwoSided':
        node['type'] = 'TWO_SIDED_AR'

    elif nodeType == 'aiUserDataColor':
        node['type'] = 'USER_DATA_COLOR_AR'
        node['colorLayer'] = cmds.getAttr(mNode + '.attribute')

        # default color is RGB tuple, need RGBA
        dColor = cmds.getAttr(mNode + '.default')[0]
        node['defaultColor'] = [dColor[0], dColor[1], dColor[2], 1.0]

    elif nodeType == 'aiUvProjection':
        node['type'] = 'UV_PROJECTION_AR'
        node['projectionType'] = cmds.getAttr(mNode + '.projectionType')
        node['coordSpace'] = cmds.getAttr(mNode + '.coordSpace')

        # convert vec3 outColor to vec2 for outUV in V3D
        node['outputs'][0].pop()

    elif nodeType == 'aiVectorToRgb':
        node['type'] = 'VECTOR_TO_RGB_AR'
        node['mode'] = cmds.getAttr(mNode + '.mode')

    elif nodeType == 'addDoubleLinear':
        node['type'] = 'ADD_DOUBLE_LINEAR_MY'

    elif nodeType == 'animCurveTA':
        node['type'] = 'ANIM_CURVE_TA_MY'

    elif nodeType == 'animCurveTU':
        node['type'] = 'ANIM_CURVE_TU_MY'

    elif nodeType == 'blendColors':
        node['type'] = 'BLEND_COLORS_MY'

    elif nodeType == 'blinn':
        node['type'] = 'BLINN_MY'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'bulge':
        node['type'] = 'BULGE_MY'

    elif nodeType == 'bump2d':
        node['type'] = 'BUMP_2D_MY'
        node['bumpInterp'] = cmds.getAttr(mNode + '.bumpInterp')

    elif nodeType == 'channels':
        node['type'] = 'CHANNELS_MY'

        node['channelR'] = cmds.getAttr(mNode + '.channelR')
        node['channelG'] = cmds.getAttr(mNode + '.channelG')
        node['channelB'] = cmds.getAttr(mNode + '.channelB')
        node['channelA'] = cmds.getAttr(mNode + '.channelA')

    elif nodeType == 'checker':
        node['type'] = 'CHECKER_MY'

    elif nodeType == 'clamp':
        node['type'] = 'CLAMP_MY'

    elif nodeType == 'cloth':
        node['type'] = 'CLOTH_MY'

    elif nodeType == 'colorComposite':
        node['type'] = 'COLOR_COMPOSITE_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'colorCondition':
        node['type'] = 'COLOR_CONDITION_MY'

    elif nodeType == 'colorConstant':
        node['type'] = 'COLOR_CONSTANT_MY'

    elif nodeType == 'colorCorrect':
        node['type'] = 'COLOR_CORRECT_MY'

        node['colClamp'] = cmds.getAttr(mNode + '.colClamp')
        node['colClampMin'] = cmds.getAttr(mNode + '.colClampMin')[0]
        node['colClampMax'] = cmds.getAttr(mNode + '.colClampMax')[0]
        node['alphaClamp'] = cmds.getAttr(mNode + '.alphaClamp')
        node['alphaClampMin'] = cmds.getAttr(mNode + '.alphaClampMin')
        node['alphaClampMax'] = cmds.getAttr(mNode + '.alphaClampMax')

        node['unpremultiplyInput'] = cmds.getAttr(mNode + '.unpremultiplyInput')
        node['premultiplyResult'] = cmds.getAttr(mNode + '.premultiplyResult')

    elif nodeType == 'colorLogic':
        node['type'] = 'COLOR_LOGIC_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'colorMask':
        node['type'] = 'COLOR_MASK_MY'
        node['maskAlphaIsLuminance'] = cmds.getAttr(mNode + '.maskAlphaIsLuminance')

    elif nodeType == 'colorMath':
        node['type'] = 'COLOR_MATH_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'composeMatrix':
        node['type'] = 'COMPOSE_MATRIX_MY'
        node['useEulerRotation'] = cmds.getAttr(mNode + '.useEulerRotation')
        node['inputRotateOrder'] = cmds.getAttr(mNode + '.inputRotateOrder')

    elif nodeType == 'condition':
        node['type'] = 'CONDITION_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'contrast':
        node['type'] = 'CONTRAST_MY'

    elif nodeType == 'decomposeMatrix':
        node['type'] = 'DECOMPOSE_MATRIX_MY'

    elif nodeType == 'envSphere':
        node['type'] = 'ENV_SPHERE_MY'

    elif nodeType == 'file':
        node['type'] = 'FILE_MY'
        node['texture'] = pU.gltf.getTextureIndex(gltf, mNode)
        node['fileHasAlpha'] = cmds.getAttr(mNode + '.fileHasAlpha')

    elif nodeType == 'floatComposite':
        node['type'] = 'FLOAT_COMPOSITE_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'floatCondition':
        node['type'] = 'FLOAT_CONDITION_MY'

    elif nodeType == 'floatConstant':
        node['type'] = 'FLOAT_CONSTANT_MY'

    elif nodeType == 'floatLogic':
        node['type'] = 'FLOAT_LOGIC_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'floatMask':
        node['type'] = 'FLOAT_MASK_MY'

    elif nodeType == 'floatMath':
        node['type'] = 'FLOAT_MATH_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'fourByFourMatrix':
        node['type'] = 'FOUR_BY_FOUR_MATRIX_MY'

    elif nodeType == 'gammaCorrect':
        node['type'] = 'GAMMA_CORRECT_MY'

    elif nodeType == 'grid':
        node['type'] = 'GRID_MY'

    elif nodeType == 'inverseMatrix':
        node['type'] = 'INVERSE_MATRIX_MY'

    elif nodeType == 'lambert':
        node['type'] = 'LAMBERT_MY'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'layeredTexture':
        node['type'] = 'LAYERED_TEXTURE_MY'

    elif nodeType == 'luminance':
        node['type'] = 'LUMINANCE_MY'

    elif nodeType == 'multiplyDivide':
        node['type'] = 'MULTIPLY_DIVIDE_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')

    elif nodeType == 'multDoubleLinear':
        node['type'] = 'MULT_DOUBLE_LINEAR_MY'

    elif nodeType == 'multMatrix':
        node['type'] = 'MULT_MATRIX_MY'

    elif nodeType == 'noise':
        node['type'] = 'NOISE_MY'
        node['noiseType'] = cmds.getAttr(mNode + '.noiseType')
        node['inputTypes'] = ['float', 'int', 'float', 'float', 'float', 'vec2', 'bool', 'float', 'float', 'float', 'vec2']

    elif nodeType == 'openPBRSurface':
        node['type'] = 'OPENPBR_SURFACE_MY'
        node['thinWalled'] = cmds.getAttr(mNode + '.geometryThinWalled')

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'phong':
        node['type'] = 'PHONG_MY'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'phongE':
        node['type'] = 'PHONG_E_MY'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'place2dTexture':
        node['type'] = 'PLACE_2D_TEXTURE_MY'

        isUVConn = False

        connsUV = cmds.listConnections(mNode + '.outUV')
        if connsUV and connsUV[0]:
            isUVConn = True
        else:
            # handle (rare) cases when only single channel is being connected
            connsU = cmds.listConnections(mNode + '.outU')
            if connsU and connsU[0]:
                isUVConn = True
            connsV = cmds.listConnections(mNode + '.outV')
            if connsV and connsV[0]:
                isUVConn = True

        if isUVConn:
            node['uvIndex'] = mayaUtils.extractTexCoordIndex(mNode, shape)

    elif nodeType == 'premultiply':
        node['type'] = 'PREMULTIPLY_MY'

    elif nodeType == 'ramp':
        node['type'] = 'RAMP_MY'
        node['rampData'] = extractRampData(mNode)
        node['rampType'] = cmds.getAttr(mNode + '.type')

        # need these for the internal ramp texture
        wrapInfo = mayaUtils.getUvWrapInfo(mNode)

        wrapS = pU.gltf.WEBGL_WRAPPINGS['REPEAT']
        wrapT = pU.gltf.WEBGL_WRAPPINGS['REPEAT']

        if wrapInfo['mirrorU']:
            wrapS = pU.gltf.WEBGL_WRAPPINGS['MIRRORED_REPEAT']
        elif not wrapInfo['wrapU']:
            wrapS = pU.gltf.WEBGL_WRAPPINGS['CLAMP_TO_EDGE']

        if wrapInfo['mirrorV']:
            wrapT = pU.gltf.WEBGL_WRAPPINGS['MIRRORED_REPEAT']
        elif not wrapInfo['wrapV']:
            wrapT = pU.gltf.WEBGL_WRAPPINGS['CLAMP_TO_EDGE']

        node['wrapS'] = wrapS
        node['wrapT'] = wrapT

    elif nodeType == 'remapColor':
        node['type'] = 'REMAP_COLOR_MY'

    elif nodeType == 'remapHsv':
        node['type'] = 'REMAP_HSV_MY'

    elif nodeType == 'remapValue':
        node['type'] = 'REMAP_VALUE_MY'

    elif nodeType == 'reverse':
        node['type'] = 'REVERSE_MY'

    elif nodeType == 'samplerInfo':
        node['type'] = 'SAMPLER_INFO_MY'

    elif nodeType == 'setRange':
        node['type'] = 'SET_RANGE_MY'

    elif nodeType == 'surfaceShader':
        node['type'] = 'SURFACE_SHADER_MY'

        # add alpha channel
        node['outputs'][0].append(0)

    elif nodeType == 'transform':
        node['type'] = 'TRANSFORM_MY'

    elif nodeType == 'transposeMatrix':
        node['type'] = 'TRANSPOSE_MATRIX_MY'

    elif nodeType == 'unitConversion':
        node['type'] = 'UNIT_CONVERSION_MY'
        node['conversionFactor'] = cmds.getAttr(mNode + '.conversionFactor')

    elif nodeType == 'unpremultiply':
        node['type'] = 'UNPREMULTIPLY_MY'

    elif nodeType == 'vectorProduct':
        node['type'] = 'VECTOR_PRODUCT_MY'
        node['operation'] = cmds.getAttr(mNode + '.operation')
        node['normalizeOutput'] = cmds.getAttr(mNode + '.normalizeOutput')

    elif nodeType == 'wtAddMatrix':
        node['type'] = 'WT_ADD_MATRIX_MY'

    else:
        log.error('Unknown node type: ' + nodeType)
        return

    verifyNodeInOuts(node['inputs'])
    verifyNodeInOuts(node['outputs'])

    nodes.append(node)
    toNode = len(nodes) - 1

    for connData in findInputConnections(mNode):

        mNodeIn = connData[3]

        if not validateNode(mNodeIn):
            continue

        # if not found, it will be added in processNode()
        fromNode = len(nodes)

        for n in nodes:
            if n['id'] == mNodeIn:
                fromNode = nodes.index(n)

        edge = {
            'fromNode' : fromNode,
            'fromOutput' : getSupportedOutputs(mNodeIn).index(connData[4]),
            'toNode' : toNode,
            'toInput': getSupportedInputs(mNode).index(connData[1])
        }

        if connData[5] >= 0:
            edge['fromChannel'] = connData[5]
        if connData[2] >= 0:
            edge['toChannel'] = connData[2]

        edges.append(edge)

        if fromNode == len(nodes):
            processNode(mNodeIn, gltf, nodes, edges, shape)


def validateNode(node):

    nodeType = cmds.objectType(node)
    if nodeType == 'file' or nodeType == 'aiImage':
        if mayaUtils.isValidFileNode(node):
            return True
        else:
            return False
    elif nodeType == 'addMatrix' or nodeType == 'multMatrix':
        if cmds.getAttr(node + '.matrixIn', multiIndices=True):
            return True
        else:
            return False
    elif nodeType in SUPPORTED_NODES:
        return True
    else:
        return False


def findInputConnections(node):
    """
    Return the list of the [toNode, toAttr, toAttrChannel, fromNode, fromAttr, fromAttrChannel]
    """

    connList = []

    attrs = getSupportedInputs(node)
    for attr in attrs:
        conns = cmds.listConnections(node + '.' + attr, plugs=True)
        if conns and conns[0]:
            for conn in conns:
                nodePrev = mayaUtils.attrNode(conn)
                nodeTypePrev = cmds.objectType(nodePrev)

                attrPrev = conn.split('.')[1]

                if nodeTypePrev in SUPPORTED_NODES:
                    attrsPrev = getSupportedOutputs(nodePrev)

                    if attrPrev in attrsPrev:
                        connList.append([node, attr, -1, nodePrev, attrPrev, -1])
                    else:
                        for attrPrevIt in attrsPrev:
                            attrsChildPrev = getChildAttrs(nodePrev, attrPrevIt)
                            if attrPrev in attrsChildPrev:
                                connList.append([node, attr, -1, nodePrev, attrPrevIt, attrsChildPrev.index(attrPrev)])

        attrsChild = getChildAttrs(node, attr)
        for attrChild in attrsChild:
            conns = cmds.listConnections(node + '.' + attrChild, plugs=True)

            if conns and conns[0]:
                for conn in conns:
                    nodePrev = mayaUtils.attrNode(conn)
                    nodeTypePrev = cmds.objectType(nodePrev)

                    attrPrev = conn.split('.')[1]

                    if nodeTypePrev in SUPPORTED_NODES:
                        attrsPrev = getSupportedOutputs(nodePrev)

                        if attrPrev in attrsPrev:
                            connList.append([node, attr, attrsChild.index(attrChild), nodePrev, attrPrev, -1])
                        else:
                            for attrPrevIt in attrsPrev:
                                attrsChildPrev = getChildAttrs(nodePrev, attrPrevIt)
                                if attrPrev in attrsChildPrev:
                                    connList.append([node, attr, attrsChild.index(attrChild),
                                                     nodePrev, attrPrevIt, attrsChildPrev.index(attrPrev)])


    return connList

def getSupportedInputs(node):
    nodeType = cmds.objectType(node)

    if nodeType == 'layeredTexture':
        inAttrs = []

        for i in cmds.getAttr(node + '.inputs', multiIndices=True):
            inAttrs.append('inputs[{}].color'.format(i))
            inAttrs.append('inputs[{}].alpha'.format(i))
            inAttrs.append('inputs[{}].blendMode'.format(i))
            inAttrs.append('inputs[{}].isVisible'.format(i))

    elif nodeType == 'addMatrix' or nodeType == 'multMatrix':
        inAttrs = []

        for i in cmds.getAttr(node + '.matrixIn', multiIndices=True):
            inAttrs.append('matrixIn[{}]'.format(i))

    elif nodeType == 'wtAddMatrix':
        inAttrs = []

        for i in cmds.getAttr(node + '.wtMatrix', multiIndices=True):
            inAttrs.append('wtMatrix[{}].matrixIn'.format(i))
            inAttrs.append('wtMatrix[{}].weightIn'.format(i))

    elif nodeType == 'aiOslShader':
        oslCode = preprocessOSL(cmds.getAttr(node + '.code'))
        oslAST = pyosl.oslparse.get_ast(oslCode)

        oslShaderName = 'node_osl_' + oslAST.get_shader_name().lower()
        oslInputs, _ = parseOSLInOuts(oslAST, oslShaderName)

        inAttrs = [i[1] for i in oslInputs]

    else:
        inAttrs = SUPPORTED_NODES[nodeType]['in']

    return inAttrs

def getSupportedOutputs(node):
    nodeType = cmds.objectType(node)
    return SUPPORTED_NODES[nodeType]['out']


def getChildAttrs(node, attr):
    """
    layeredTexture, inputs[0].color -> inputs[0].colorR, inputs[0].colorG ...
    """

    childAttrs = []

    path = node + '.' + attr

    nodeQuery, attrQuery = path.rsplit('.', 1)

    # ignore cases such as addMatrix.matrixIn[0]
    if re.search(r'\[\d+\]', attrQuery):
        return childAttrs

    childsQuery = cmds.attributeQuery(attrQuery, node=nodeQuery, listChildren=True)
    if childsQuery:
        for c in childsQuery:
            childAttrs.append(attr.replace(attrQuery, c))

    return childAttrs


def genNodeInOuts(mNode, attrs):

    inouts = []

    for attr in attrs:
        inouts.append(nodeAttrValue(mNode, attr))

    return inouts

def nodeAttrValue(mNode, attr):
    type = cmds.getAttr(mNode + '.' + attr, type=True)
    # message attributes do not transmit any data
    value = cmds.getAttr(mNode + '.' + attr) if type != 'message' else None

    if value is not None:
        if type == 'float2' or type == 'float3' or type == 'double3' or type == 'double4':
            # lists are easier to modify, e.g add alpha channel
            value = list(value[0])
        elif type == 'float':
            pass
        elif type == 'double':
            pass
        elif type == 'doubleLinear':
            pass
        elif type == 'doubleAngle':
            value = math.radians(value)
        elif type == 'short' or type == 'long':
            pass
        elif type == 'matrix':
            pass
        elif type == 'bool':
            pass
        elif type == 'enum':
            pass
        elif type == 'typed':
            pass
        elif type == 'string':
            # we need it for OSL shaders only
            value = pyosl.glslgen.string_to_osl_const(value)
        else:
            log.error('Unknown node input type: ' + type)
    else:
        value = 0

    return value

def extractTextures(mNode, textures):

    if not validateNode(mNode):
        return

    for connData in findInputConnections(mNode):
        mNodeIn = connData[3]

        if (cmds.objectType(mNodeIn) == 'file' or cmds.objectType(mNodeIn) == 'aiImage') and validateNode(mNodeIn) and mNodeIn not in textures:
            textures.append(mNodeIn)

        extractTextures(mNodeIn, textures)

def hasNormalMap(mMat):

    hasNormalMap = [False]

    def setHasNormalMap(node):
        nodeType = cmds.objectType(node)
        if nodeType == 'bump2d' or nodeType == 'aiNormalMap':
            hasNormalMap[0] = True

    traverseMaterial(mMat, setHasNormalMap)

    return hasNormalMap[0]


def extractAnimNodes(mNode, animNodes=None):
    # do not use [] as default argument!
    if animNodes is None:
        animNodes = []

    if not validateNode(mNode):
        return animNodes

    for connData in findInputConnections(mNode):
        mNodeIn = connData[3]

        if (cmds.objectType(mNodeIn) == 'animCurveTU' and validateNode(mNodeIn) and
                cmds.keyframe(mNodeIn, query=True, keyframeCount=True) and
                mNodeIn not in animNodes):
            animNodes.append(mNodeIn)

        extractAnimNodes(mNodeIn, animNodes)

    return animNodes

def traverseMaterial(mNode, callback):

    if not validateNode(mNode):
        return

    callback(mNode)

    for connData in findInputConnections(mNode):
        mNodeIn = connData[3]
        if validateNode(mNodeIn):
            traverseMaterial(mNodeIn, callback)


def listAttrs(mNode):

    print('=== ' + cmds.objectType(mNode) + ' ===')

    for attr in cmds.listAttr(mNode, connectable=True, visible=True):
        if '.' not in attr:
            print(attr, cmds.getAttr(mNode + '.' + attr, type=True))

def extractRampData(ramp):

    keyIndices = cmds.getAttr(ramp + '.colorEntryList', multiIndices=True)

    def getKeyPos(i):
        return cmds.getAttr(ramp + '.colorEntryList[{}].position'.format(str(i)))

    def getKeyColor(i):
        return cmds.getAttr(ramp + '.colorEntryList[{}].color'.format(str(i)))[0]

    keyIndices.sort(key=lambda i: getKeyPos(i))
    keyPositions = [getKeyPos(i) for i in keyIndices]
    keyColors = [getKeyColor(i) for i in keyIndices]

    # insert missing start/end keys
    if keyPositions[0] != 0:
        keyPositions.insert(0, 0)
        keyColors.insert(0, keyColors[0])
    if keyPositions[-1] != 1:
        keyPositions.append(1)
        keyColors.append(keyColors[-1])

    rampData = []

    interp = cmds.getAttr(ramp + '.interpolation')

    posFromIdx = 0
    posFrom = keyPositions[0]
    posTo = keyPositions[1]
    for i in range(RAMP_DATA_SIZE):
        samplePos = i / (RAMP_DATA_SIZE - 1)

        while samplePos > posTo:
            posFromIdx += 1
            posFrom = posTo
            posTo = keyPositions[posFromIdx + 1]

        t = (samplePos - posFrom) / (posTo - posFrom)
        colFrom = keyColors[posFromIdx]
        colTo = keyColors[posFromIdx + 1]


        if interp == 0:
            coeff = 0
        elif interp == 1:
            coeff = t
        elif interp == 2:
            coeff = t * t
        elif interp == 3:
            coeff = 1 - (1-t) * (1-t)
        elif interp == 4:
            coeff = 0.5 * (math.cos((t+1) * math.pi) + 1)
        elif interp == 5:
            lumFrom = pU.math.colorToLuminosity(colFrom)
            lumTo = pU.math.colorToLuminosity(colTo)

            if lumFrom < lumTo:
                coeff = math.sin(t * math.pi/2)
            else:
                coeff = math.sin((t-1) * math.pi/2) + 1
        else:
            lumFrom = pU.math.colorToLuminosity(colFrom)
            lumTo = pU.math.colorToLuminosity(colTo)

            if lumFrom > lumTo:
                coeff = math.sin(t * math.pi/2)
            else:
                coeff = math.sin((t-1) * math.pi/2) + 1

        r = colFrom[0] * (1 - coeff) + colTo[0] * coeff
        g = colFrom[1] * (1 - coeff) + colTo[1] * coeff
        b = colFrom[2] * (1 - coeff) + colTo[2] * coeff

        rampData.append(r)
        rampData.append(g)
        rampData.append(b)

    return rampData


def preprocessOSL(code):
    out = io.StringIO()

    p = pcpp.Preprocessor()
    p.line_directive = None
    p.parse(code)
    p.write(out)

    return out.getvalue()

def parseOSLInOuts(ast, shaderName):

    inputs, outputs = ast.get_shader_params()

    def typeToVal(type):
        if type in ['point', 'vector', 'normal', 'color']:
            return [0, 0, 0]
        else:
            return 0

    def typeToGLSLType(type):
        if type in ['point', 'vector', 'normal', 'color']:
            return 'vec3'
        elif type in ['int', 'string']:
            return 'int'
        else:
            return 'float'

    def getInitCode(ast, n):
        if ast is None:
            return None
        return genOSLCode(ast, shaderName + '_init_' + str(n))

    def getInitGlobVars(ast):
        if ast is None:
            return None
        return [varName for _, varName in pyosl.glslgen.find_global_variables(ast)]

    inputs = [(typeToGLSLType(i[0]), i[1], typeToVal(i[0]), getInitCode(i[2], inputs.index(i)), getInitGlobVars(i[2])) for i in inputs]
    outputs = [(typeToGLSLType(o[0]), o[1], typeToVal(o[0])) for o in outputs]

    return inputs, outputs

def genOSLCode(ast, shaderName):
    ast = pyosl.glslgen.osl_to_glsl(ast)
    pyosl.glslgen.rename_shader(ast, shaderName)
    code = pyosl.glslgen.generate(ast)
    return code
