module.exports = function transform(node, compiler, template) {

    //Find and handle nested <attrs> elements
    findNestedAttrs(node, compiler, template);

    var tag;


    function convertAttrValue(attr, type, attrDef) {
        type = type || (attrDef ? attrDef.type : 'string') || 'string';

        try {
            return compiler.convertType(attr.value, type, attrDef ? attrDef.allowExpressions !== false : true);
        } catch (e) {
            node.addError('Invalid attribute value of "' + attr.value + '" for attribute "' + attr.name + '": ' + e.message);
            return attr.value;
        }
    }

    function forEachProp(callback, thisObj) {
        var foundProps = {};

        node.forEachAttributeAnyNS(function (attr) {
            var attrDef = compiler.taglibs.getAttribute(node, attr);
            if (!attrDef) {
                // var isAttrForTaglib = compiler.taglibs.isTaglib(attrUri);
                //Tag doesn't allow dynamic attributes
                node.addError('The tag "' + tag.name + '" in taglib "' + tag.taglibId + '" does not support attribute "' + attr + '"');
                return;
            }

            var type = attrDef.type || 'string';

            var value;

            if (compiler.isExpression(attr.value)) {
                value = attr.value;
            } else {
                if (type === 'path') {
                    var pathVar;
                    if (compiler.hasExpression(attr.value)) {
                        value = convertAttrValue(attr, 'string', attrDef);
                    } else {
                        // Resolve the static string to a full path only once
                        pathVar = template.addStaticVar(attr.value, 'require.resolve(' + compiler.convertType(attr.value, 'string', true) + ')');
                        value = compiler.makeExpression(pathVar);
                    }
                } else if (type === 'template') {
                    var templateVar;
                    if (compiler.hasExpression(attr.value)) {
                        value = compiler.makeExpression('__helpers.l(' + convertAttrValue(attr, 'string', attrDef) + ')');
                    } else {
                        // Resolve the static string to a full path only once
                        templateVar = template.addStaticVar(attr.value, '__helpers.l(require.resolve(' + compiler.convertType(attr.value, 'string', true) + '))');
                        value = compiler.makeExpression(templateVar);
                    }
                } else {
                    value = convertAttrValue(attr, type, attrDef);
                }
            }
            var propName;
            if (attrDef.dynamicAttribute) {
                propName = attr.qName;
            } else {
                if (attrDef.targetProperty) {
                    propName = attrDef.targetProperty;
                } else if (attrDef.preserveName) {
                    propName = attr.localName;
                } else {
                    propName = removeDashes(attr.localName);
                }
            }

            foundProps[propName] = true;
            callback.call(thisObj, propName, value, attrDef);
        });

        tag.forEachAttribute(function (attr) {
            if (attr.hasOwnProperty('defaultValue') && !foundProps[attr.name]) {
                callback.call(thisObj, attr.name, template.makeExpression(JSON.stringify(attr.defaultValue)), '', attr);
            }
        });
    }

    tag = node.tag || compiler.taglibs.getTag(node);

    var transformer = new Transformer(template, compiler);
    node = transformer.transformNode(node);
    var inputAttr = transformer.inputAttr;

    if (tag) {
        if (tag.preserveWhitespace) {
            node.setPreserveWhitespace(true);
        }
        if (tag.renderer || tag.template) {
            if (tag.renderer) {
                //Instead of compiling as a static XML element, we'll
                //make the node render as a tag handler node so that
                //writes code that invokes the handler
                TagHandlerNode.convertNode(node, tag);
                if (inputAttr) {
                    node.setInputExpression(template.makeExpression(inputAttr));
                }
            } else {
                var templatePath = resolver.deresolve(tag.template, compiler.dirname);
                // The tag is mapped to a template that will be used to perform
                // the rendering so convert the node into a "IncludeNode" that can
                // be used to include the output of rendering a template
                IncludeNode.convertNode(node, templatePath);
            }

            forEachProp(function (name, value, attrDef) {
                if (attrDef.dynamicAttribute && attrDef.targetProperty) {
                    if (attrDef.removeDashes === true) {
                        name = removeDashes(name);
                    }
                    if (node.addDynamicAttribute) {
                        node.addDynamicAttribute(name, value);
                        node.setDynamicAttributesProperty(attrDef.targetProperty);
                    } else {
                        node.setProperty(name, value);
                    }
                } else {
                    node.setProperty(name, value);
                }
            });

        } else if (tag.nodeClass) {
            var NodeCompilerClass = require(tag.nodeClass);
            compiler.inheritNode(NodeCompilerClass);
            extend(node, NodeCompilerClass.prototype);
            NodeCompilerClass.call(node);
            node.setNodeClass(NodeCompilerClass);
            forEachProp(function (name, value) {
                node.setProperty(name, value);
            });
        }
    }
};
    doGenerateCode: function (template) {
        var rendererPath = raptorModulesResolver.deresolve(this.tag.renderer, template.dirname);
        var handlerVar = addHandlerVar(template, rendererPath);

        this.tag.forEachImportedVariable(function (importedVariable) {
            this.setProperty(importedVariable.targetProperty, template.makeExpression(importedVariable.expression));
        }, this);
        
        var _this = this;
        var variableNames = [];
        _this.tag.forEachVariable(function (nestedVar) {
            var varName;
            if (nestedVar.nameFromAttribute) {
                var possibleNameAttributes = nestedVar.nameFromAttribute.split(/\s+or\s+|\s*,\s*/i);
                for (var i = 0, len = possibleNameAttributes.length; i < len; i++) {
                    var attrName = possibleNameAttributes[i];
                    var keep = false;
                    if (attrName.endsWith('|keep')) {
                        keep = true;
                        attrName = attrName.slice(0, 0 - '|keep'.length);
                        possibleNameAttributes[i] = attrName;
                    }
                    varName = this.getAttribute(attrName);
                    if (varName) {
                        if (!keep) {
                            this.removeProperty(attrName);
                        }
                        break;
                    }
                }
                if (!varName) {
                    this.addError('Attribute ' + possibleNameAttributes.join(' or ') + ' is required');
                    varName = '_var';    // Let it continue with errors
                }
            } else {
                varName = nestedVar.name;
                if (!varName) {
                    this.addError('Variable name is required');
                    varName = '_var';    // Let it continue with errors
                }
            }
            variableNames.push(varName);
        }, this);
        if (_this.preInvokeCode.length) {
            _this.preInvokeCode.forEach(function (code) {
                template.indent().code(code).code('\n');
            });
        }

        

        template.contextHelperMethodCall('t', function () {
            template.code('\n').indent(function () {
                template.line(handlerVar + ',').indent();
                if (_this.inputExpression) {
                    template.code(_this.inputExpression);
                } else {
                    if (_this.dynamicAttributes) {
                        template.indent(function() {
                            _this.setProperty(_this.dynamicAttributesProperty, template.makeExpression(getPropsStr(_this.dynamicAttributes, template)));
                        });
                    }

                    template.code(getPropsStr(_this.getProperties(), template));
                }
                if (_this.hasChildren()) {
                    var bodyParams = [];
                    variableNames.forEach(function (varName) {
                        bodyParams.push(varName);
                    });
                    template.code(',\n').line('function(' + bodyParams.join(',') + ') {').indent(function () {
                        _this.generateCodeForChildren(template);
                    }).indent().code('}');
                }
            });
        });

        if (_this.postInvokeCode.length) {
            _this.postInvokeCode.forEach(function (code) {
                template.indent().code(code).code('\n');
            });
        }
    }