/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Tools;
using OpenSim.Region.Framework.Interfaces;

namespace OpenSim.Region.ScriptEngine.Shared.CodeTools
{
    public class CSCodeGenerator : ICodeConverter
    {
//      private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        private static yyLSLSyntax yyLSL = new yyLSLSyntax();
        private SYMBOL m_astRoot = null;
        private Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> m_positionMap;
        private int m_braceCount;       // for indentation
        private int m_CSharpLine;       // the current line of generated C# code
        private int m_CSharpCol;        // the current column of generated C# code
        private List<string> m_warnings = new List<string>();
        private IScriptModuleComms m_comms = null;

        private bool m_insertCoopTerminationChecks;
        private static string m_coopTerminationCheck = "opensim_reserved_CheckForCoopTermination();";

        /// <summary>
        /// Keep a record of the previous node when we do the parsing.
        /// </summary>
        /// <remarks>
        /// We do this here because the parser generated by CSTools does not retain a reference to its parent node.
        /// The previous node is required so we can correctly insert co-op termination checks when required.
        /// </remarks>
//        private SYMBOL m_previousNode;

        /// <summary>
        /// Creates an 'empty' CSCodeGenerator instance.
        /// </summary>
        public CSCodeGenerator()
        {
            m_comms = null;
            ResetCounters();
        }

        public CSCodeGenerator(IScriptModuleComms comms, bool insertCoopTerminationChecks)
        {
            m_comms = comms;
            m_insertCoopTerminationChecks = insertCoopTerminationChecks;
            ResetCounters();
        }

        /// <summary>
        /// Get the mapping between LSL and C# line/column number.
        /// </summary>
        /// <returns>Dictionary\<KeyValuePair\<int, int\>, KeyValuePair\<int, int\>\>.</returns>
        public Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> PositionMap
        {
            get { return m_positionMap; }
        }

        /// <summary>
        /// Get the mapping between LSL and C# line/column number.
        /// </summary>
        /// <returns>SYMBOL pointing to root of the abstract syntax tree.</returns>
        public SYMBOL ASTRoot
        {
            get { return m_astRoot; }
        }

        public void Clear()
        {
            m_astRoot.kids = null;
            m_astRoot.yylx = null;
            m_astRoot.yyps = null;
            m_astRoot = null;
            m_positionMap = null;
            m_warnings.Clear();
            m_comms = null;
        }
        /// <summary>
        /// Resets various counters and metadata.
        /// </summary>
        private void ResetCounters()
        {
            m_braceCount = 0;
            m_CSharpLine = 0;
            m_CSharpCol = 1;
            m_positionMap = new Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>>();
            m_astRoot = null;
        }

        public string Convert(string script)
        {
            StringBuilder sb = new StringBuilder(4096);
            Convert(script, sb);
            return sb.ToString();
        }

        /// <summary>
        /// Generate the code from the AST we have.
        /// </summary>
        /// <param name="script">The LSL source as a string.</param>
        /// <returns>String containing the generated C# code.</returns>
        public void Convert(string script, StringBuilder sb)
        {
//            m_log.DebugFormat("[CS CODE GENERATOR]: Converting to C#\n{0}", script);

            m_warnings.Clear();
            ResetCounters();
            ErrorHandler errorHandler = new ErrorHandler(true);
            Parser p = new LSLSyntax(yyLSL, errorHandler);

            LSL2CSCodeTransformer codeTransformer;
            try
            {
                codeTransformer = new LSL2CSCodeTransformer(p.Parse(script));
            }
            catch (CSToolsException e)
            {
                string message;

                // LL start numbering lines at 0 - geeks!
                // Also need to subtract one line we prepend!
                //
                string emessage = e.Message;
                string slinfo = e.slInfo.ToString();

                // Remove wrong line number info
                //
                if (emessage.StartsWith(slinfo+": "))
                    emessage = emessage.Substring(slinfo.Length+2);

                message = String.Format("({0},{1}) {2}",
                        e.slInfo.lineNumber - 1,
                        e.slInfo.charPosition - 1, emessage);

                throw new Exception(message);
            }

            m_astRoot = codeTransformer.Transform();


            // standard preamble



            m_braceCount++;
            m_braceCount++;

            // line number
            m_CSharpLine += 10;

            // here's the payload
            sb.Append("\n");
            foreach (SYMBOL s in m_astRoot.kids)
                GenerateNodeToSB(m_astRoot, s, sb);

            codeTransformer = null;
            p.m_lexer.m_buf=null;
            p.m_lexer.yytext = null;
            p.m_lexer = null;
            p.m_symbols = null;
            p = null;
            errorHandler = null;

            // close braces!
//            m_braceCount--;
            //retstr += GenerateIndentedLine("}");
//            m_braceCount--;
            //retstr += GenerateLine("}");

            // Removes all carriage return characters which may be generated in Windows platform. Is there
            // cleaner way of doing this?
//            sb.Replace("\r", "");
        }

        /// <summary>
        /// Get the set of warnings generated during compilation.
        /// </summary>
        /// <returns></returns>
        public string[] GetWarnings()
        {
            return m_warnings.ToArray();
        }

        private void AddWarning(string warning)
        {
            if (!m_warnings.Contains(warning))
            {
                m_warnings.Add(warning);
            }
        }

        /// <summary>
        /// Recursively called to generate each type of node. Will generate this
        /// node, then all it's children.
        /// </summary>
        /// <param name="previousSymbol">The parent node.</param>
        /// <param name="s">The current node to generate code for.</param>
        /// <returns>String containing C# code for SYMBOL s.</returns>
        private void GenerateNodeToSB(SYMBOL previousSymbol, SYMBOL s, StringBuilder sb)
        {
            // make sure to put type lower in the inheritance hierarchy first
            // ie: since IdentArgument and ExpressionArgument inherit from
            // Argument, put IdentArgument and ExpressionArgument before Argument
            if (s is GlobalFunctionDefinition)
                GenerateGlobalFunctionDefinition((GlobalFunctionDefinition) s, sb);
            else if (s is GlobalVariableDeclaration)
                GenerateGlobalVariableDeclaration((GlobalVariableDeclaration) s , sb);
            else if (s is State)
                GenerateState((State) s, sb);
            else if (s is CompoundStatement)
                GenerateCompoundStatement(previousSymbol, (CompoundStatement) s, sb);
            else if (s is Declaration)
                GenerateDeclaration((Declaration) s, sb);
            else if (s is Statement)
                GenerateStatement(previousSymbol, (Statement) s, sb);
            else if (s is ReturnStatement)
                GenerateReturnStatement((ReturnStatement) s, sb);
            else if (s is JumpLabel)
                GenerateJumpLabel((JumpLabel) s, sb);
            else if (s is JumpStatement)
                GenerateJumpStatement((JumpStatement) s, sb);
            else if (s is StateChange)
                GenerateStateChange((StateChange) s, sb);
            else if (s is IfStatement)
                GenerateIfStatement((IfStatement) s, sb);
            else if (s is WhileStatement)
                GenerateWhileStatement((WhileStatement) s, sb);
            else if (s is DoWhileStatement)
                GenerateDoWhileStatement((DoWhileStatement) s, sb);
            else if (s is ForLoop)
                GenerateForLoop((ForLoop) s, sb);
            else if (s is ArgumentList)
                GenerateArgumentList((ArgumentList) s, sb);
            else if (s is Assignment)
                GenerateAssignment((Assignment) s, sb);
            else if (s is BinaryExpression)
                GenerateBinaryExpression((BinaryExpression) s, sb);
            else if (s is ParenthesisExpression)
                GenerateParenthesisExpression((ParenthesisExpression) s, sb);
            else if (s is UnaryExpression)
                GenerateUnaryExpression((UnaryExpression) s, sb);
            else if (s is IncrementDecrementExpression)
                GenerateIncrementDecrementExpression((IncrementDecrementExpression) s, sb);
            else if (s is TypecastExpression)
                GenerateTypecastExpression((TypecastExpression) s, sb);
            else if (s is FunctionCall)
                GenerateFunctionCall((FunctionCall) s, sb);
            else if (s is VectorConstant)
                GenerateVectorConstant((VectorConstant) s, sb);
            else if (s is RotationConstant)
                GenerateRotationConstant((RotationConstant) s, sb);
            else if (s is ListConstant)
                GenerateListConstant((ListConstant) s, sb);
            else if (s is Constant)
                GenerateConstant((Constant) s, sb);
            else if (s is IdentDotExpression)
                Generate(CheckName(((IdentDotExpression) s).Name) + "." + ((IdentDotExpression) s).Member, s, sb);
            else if (s is IdentExpression)
                GenerateIdentifier(((IdentExpression) s).Name, s, sb);
            else if (s is IDENT)
                Generate(CheckName(((TOKEN) s).yytext), s, sb);
            else
            {
                foreach (SYMBOL kid in s.kids)
                    GenerateNodeToSB(s, kid,sb);
            }

            return;
        }

        /// <summary>
        /// Generates the code for a GlobalFunctionDefinition node.
        /// </summary>
        /// <param name="gf">The GlobalFunctionDefinition node.</param>
        /// <returns>String containing C# code for GlobalFunctionDefinition gf.</returns>
        private void GenerateGlobalFunctionDefinition(GlobalFunctionDefinition gf, StringBuilder sb)
        {
            // we need to separate the argument declaration list from other kids
            List<SYMBOL> argumentDeclarationListKids = new List<SYMBOL>();
            List<SYMBOL> remainingKids = new List<SYMBOL>();

            foreach (SYMBOL kid in gf.kids)
                if (kid is ArgumentDeclarationList)
                    argumentDeclarationListKids.Add(kid);
                else
                    remainingKids.Add(kid);

            GenerateIndented(String.Format("{0} {1}(", gf.ReturnType, CheckName(gf.Name)), gf, sb);

            // print the state arguments, if any
            foreach (SYMBOL kid in argumentDeclarationListKids)
                GenerateArgumentDeclarationList((ArgumentDeclarationList) kid, sb);

            GenerateLine(")", sb);

            foreach (SYMBOL kid in remainingKids)
                GenerateNodeToSB(gf, kid,sb);
        }

        /// <summary>
        /// Generates the code for a GlobalVariableDeclaration node.
        /// </summary>
        /// <param name="gv">The GlobalVariableDeclaration node.</param>
        /// <returns>String containing C# code for GlobalVariableDeclaration gv.</returns>
        private void GenerateGlobalVariableDeclaration(GlobalVariableDeclaration gv, StringBuilder sb)
        {
            foreach (SYMBOL s in gv.kids)
            {
                Indent(sb);
                GenerateNodeToSB(gv, s ,sb);
                GenerateLine(";", sb);
            }
        }

        /// <summary>
        /// Generates the code for a State node.
        /// </summary>
        /// <param name="s">The State node.</param>
        /// <returns>String containing C# code for State s.</returns>
        private void GenerateState(State s, StringBuilder sb)
        {
            foreach (SYMBOL kid in s.kids)
                if (kid is StateEvent)
                    GenerateStateEvent((StateEvent) kid, s.Name, sb);
        }

        /// <summary>
        /// Generates the code for a StateEvent node.
        /// </summary>
        /// <param name="se">The StateEvent node.</param>
        /// <param name="parentStateName">The name of the parent state.</param>
        /// <returns>String containing C# code for StateEvent se.</returns>
        private void GenerateStateEvent(StateEvent se, string parentStateName, StringBuilder sb)
        {
            // we need to separate the argument declaration list from other kids
            List<SYMBOL> argumentDeclarationListKids = new List<SYMBOL>();
            List<SYMBOL> remainingKids = new List<SYMBOL>();

            foreach (SYMBOL kid in se.kids)
                if (kid is ArgumentDeclarationList)
                    argumentDeclarationListKids.Add(kid);
                else
                    remainingKids.Add(kid);

            // "state" (function) declaration
            GenerateIndented(String.Format("public void {0}_event_{1}(", parentStateName, se.Name), se , sb);

            // print the state arguments, if any
            foreach (SYMBOL kid in argumentDeclarationListKids)
                GenerateArgumentDeclarationList((ArgumentDeclarationList) kid, sb);

            GenerateLine(")", sb);

            foreach (SYMBOL kid in remainingKids)
                GenerateNodeToSB(se, kid, sb);
        }

        /// <summary>
        /// Generates the code for an ArgumentDeclarationList node.
        /// </summary>
        /// <param name="adl">The ArgumentDeclarationList node.</param>
        /// <returns>String containing C# code for ArgumentDeclarationList adl.</returns>
        private void GenerateArgumentDeclarationList(ArgumentDeclarationList adl, StringBuilder sb)
        {
            int comma = adl.kids.Count - 1; // tells us whether to print a comma

            foreach (Declaration d in adl.kids)
            {
                Generate(String.Format("{0} {1}", d.Datatype, CheckName(d.Id)), d, sb);
                if (0 < comma--)
                    Generate(", ", sb);
            }
        }

        /// <summary>
        /// Generates the code for an ArgumentList node.
        /// </summary>
        /// <param name="al">The ArgumentList node.</param>
        /// <returns>String containing C# code for ArgumentList al.</returns>
        private void GenerateArgumentList(ArgumentList al, StringBuilder sb)
        {
            int comma = al.kids.Count - 1;  // tells us whether to print a comma

            foreach (SYMBOL s in al.kids)
            {
                GenerateNodeToSB(al, s, sb);
                if (0 < comma--)
                    Generate(", ", sb);
            }
        }

        /// <summary>
        /// Generates the code for a CompoundStatement node.
        /// </summary>
        /// <param name="cs">The CompoundStatement node.</param>
        /// <returns>String containing C# code for CompoundStatement cs.</returns>
        private void GenerateCompoundStatement(SYMBOL previousSymbol, CompoundStatement cs, StringBuilder sb)
        {
            // opening brace
            GenerateIndentedLine("{", sb);
            m_braceCount++;

            if (m_insertCoopTerminationChecks)
            {
                // We have to check in event functions as well because the user can manually call these.
                if (previousSymbol is GlobalFunctionDefinition
                    || previousSymbol is WhileStatement
                    || previousSymbol is DoWhileStatement
                    || previousSymbol is ForLoop
                    || previousSymbol is StateEvent)
                GenerateIndentedLine(m_coopTerminationCheck, sb);
            }

            foreach (SYMBOL kid in cs.kids)
                GenerateNodeToSB(cs, kid, sb);

            // closing brace
            m_braceCount--;
            GenerateIndentedLine("}", sb);
        }

        /// <summary>
        /// Generates the code for a Declaration node.
        /// </summary>
        /// <param name="d">The Declaration node.</param>
        /// <returns>String containing C# code for Declaration d.</returns>
        private void GenerateDeclaration(Declaration d, StringBuilder sb)
        {
            Generate(String.Format("{0} {1}", d.Datatype, CheckName(d.Id)), d, sb);
        }

        /// <summary>
        /// Generates the code for a Statement node.
        /// </summary>
        /// <param name="s">The Statement node.</param>
        /// <returns>String containing C# code for Statement s.</returns>
        private void GenerateStatement(SYMBOL previousSymbol, Statement s, StringBuilder sb)
        {
            string retstr = String.Empty;
            bool printSemicolon = true;
            bool transformToBlock = false;

            if (m_insertCoopTerminationChecks)
            {
                // A non-braced single line do while structure cannot contain multiple statements.
                // So to insert the termination check we change this to a braced control structure instead.
                if (previousSymbol is WhileStatement
                    || previousSymbol is DoWhileStatement
                    || previousSymbol is ForLoop)
                {
                    transformToBlock = true;

                    // FIXME: This will be wrongly indented because the previous for/while/dowhile will have already indented.
                    GenerateIndentedLine("{", sb);

                    GenerateIndentedLine(m_coopTerminationCheck, sb);
                }
            }

            Indent(sb);

            if (0 < s.kids.Count)
            {
                // Jump label prints its own colon, we don't need a semicolon.
                printSemicolon = !(s.kids.Top is JumpLabel);

                // If we encounter a lone Ident, we skip it, since that's a C#
                // (MONO) error.
                if (!(s.kids.Top is IdentExpression && 1 == s.kids.Count))
                    foreach (SYMBOL kid in s.kids)
                        GenerateNodeToSB(s, kid, sb);
            }

            if (printSemicolon)
                GenerateLine(";", sb);

            if (transformToBlock)
            {
                // FIXME: This will be wrongly indented because the for/while/dowhile is currently handling the unindent
                GenerateIndentedLine("}", sb);
            }
        }

        /// <summary>
        /// Generates the code for an Assignment node.
        /// </summary>
        /// <param name="a">The Assignment node.</param>
        /// <returns>String containing C# code for Assignment a.</returns>
        private void GenerateAssignment(Assignment a, StringBuilder sb)
        {
            List<string> identifiers = new List<string>();
            checkForMultipleAssignments(identifiers, a);

            GenerateNodeToSB(a, (SYMBOL) a.kids.Pop(), sb);
            Generate(String.Format(" {0} ", a.AssignmentType), a, sb);
            foreach (SYMBOL kid in a.kids)
                GenerateNodeToSB(a, kid, sb);
        }

        // This code checks for LSL of the following forms, and generates a
        // warning if it finds them.
        //
        // list l = [ "foo" ];
        // l = (l=[]) + l + ["bar"];
        // (produces l=["foo","bar"] in SL but l=["bar"] in OS)
        //
        // integer i;
        // integer j;
        // i = (j = 3) + (j = 4) + (j = 5);
        // (produces j=3 in SL but j=5 in OS)
        //
        // Without this check, that code passes compilation, but does not do what
        // the end user expects, because LSL in SL evaluates right to left instead
        // of left to right.
        //
        // The theory here is that producing an error and alerting the end user that
        // something needs to change is better than silently generating incorrect code.
        private void checkForMultipleAssignments(List<string> identifiers, SYMBOL s)
        {
            if (s is Assignment)
            {
                Assignment a = (Assignment)s;
                string newident = null;

                if (a.kids[0] is Declaration)
                {
                    newident = ((Declaration)a.kids[0]).Id;
                }
                else if (a.kids[0] is IDENT)
                {
                    newident = ((IDENT)a.kids[0]).yytext;
                }
                else if (a.kids[0] is IdentDotExpression)
                {
                    newident = ((IdentDotExpression)a.kids[0]).Name; // +"." + ((IdentDotExpression)a.kids[0]).Member;
                }
                else
                {
                    AddWarning(String.Format("Multiple assignments checker internal error '{0}' at line {1} column {2}.", a.kids[0].GetType(), ((SYMBOL)a.kids[0]).Line - 1, ((SYMBOL)a.kids[0]).Position));
                }

                if (identifiers.Contains(newident))
                {
                    AddWarning(String.Format("Multiple assignments to '{0}' at line {1} column {2}; results may differ between LSL and OSSL.", newident, ((SYMBOL)a.kids[0]).Line - 1, ((SYMBOL)a.kids[0]).Position));
                }
                identifiers.Add(newident);
            }

            int index;
            for (index = 0; index < s.kids.Count; index++)
            {
                checkForMultipleAssignments(identifiers, (SYMBOL) s.kids[index]);
            }
        }

        /// <summary>
        /// Generates the code for a ReturnStatement node.
        /// </summary>
        /// <param name="rs">The ReturnStatement node.</param>
        /// <returns>String containing C# code for ReturnStatement rs.</returns>
        private void GenerateReturnStatement(ReturnStatement rs, StringBuilder sb)
        {
            Generate("return ", rs, sb);

            foreach (SYMBOL kid in rs.kids)
                GenerateNodeToSB(rs, kid, sb);
        }

        /// <summary>
        /// Generates the code for a JumpLabel node.
        /// </summary>
        /// <param name="jl">The JumpLabel node.</param>
        /// <returns>String containing C# code for JumpLabel jl.</returns>
        private void GenerateJumpLabel(JumpLabel jl, StringBuilder sb)
        {
            string labelStatement;

            if (m_insertCoopTerminationChecks)
                labelStatement = m_coopTerminationCheck;
            else
                labelStatement = "NoOp();";

            GenerateLine(String.Format("{0}: {1}", CheckName(jl.LabelName), labelStatement), jl, sb);
        }

        /// <summary>
        /// Generates the code for a JumpStatement node.
        /// </summary>
        /// <param name="js">The JumpStatement node.</param>
        /// <returns>String containing C# code for JumpStatement js.</returns>
        private void GenerateJumpStatement(JumpStatement js, StringBuilder sb)
        {
            Generate(String.Format("goto {0}", CheckName(js.TargetName)), js, sb);
        }

        /// <summary>
        /// Generates the code for an IfStatement node.
        /// </summary>
        /// <param name="ifs">The IfStatement node.</param>
        /// <returns>String containing C# code for IfStatement ifs.</returns>
        private void GenerateIfStatement(IfStatement ifs, StringBuilder sb)
        {
            GenerateIndented("if (", ifs, sb);
            GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
            GenerateLine(")", sb);

            // CompoundStatement handles indentation itself but we need to do it
            // otherwise.
            bool indentHere = ifs.kids.Top is Statement;
            if (indentHere) m_braceCount++;
            GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
            if (indentHere) m_braceCount--;

            if (0 < ifs.kids.Count) // do it again for an else
            {
                GenerateIndentedLine("else", ifs, sb);

                indentHere = ifs.kids.Top is Statement;
                if (indentHere) m_braceCount++;
                GenerateNodeToSB(ifs, (SYMBOL) ifs.kids.Pop(), sb);
                if (indentHere) m_braceCount--;
            }
        }

        /// <summary>
        /// Generates the code for a StateChange node.
        /// </summary>
        /// <param name="sc">The StateChange node.</param>
        /// <returns>String containing C# code for StateChange sc.</returns>
        private void GenerateStateChange(StateChange sc, StringBuilder sb)
        {
            Generate(String.Format("state(\"{0}\")", sc.NewState), sc, sb);
        }

        /// <summary>
        /// Generates the code for a WhileStatement node.
        /// </summary>
        /// <param name="ws">The WhileStatement node.</param>
        /// <returns>String containing C# code for WhileStatement ws.</returns>
        private void GenerateWhileStatement(WhileStatement ws, StringBuilder sb)
        {
            GenerateIndented("while (", ws, sb);
            GenerateNodeToSB(ws, (SYMBOL) ws.kids.Pop(), sb);
            GenerateLine(")", sb);

            // CompoundStatement handles indentation itself but we need to do it
            // otherwise.
            bool indentHere = ws.kids.Top is Statement;
            if (indentHere) m_braceCount++;
            GenerateNodeToSB(ws, (SYMBOL) ws.kids.Pop(), sb);
            if (indentHere) m_braceCount--;
        }

        /// <summary>
        /// Generates the code for a DoWhileStatement node.
        /// </summary>
        /// <param name="dws">The DoWhileStatement node.</param>
        /// <returns>String containing C# code for DoWhileStatement dws.</returns>
        private void GenerateDoWhileStatement(DoWhileStatement dws, StringBuilder sb)
        {
            GenerateIndentedLine("do", dws, sb);

            // CompoundStatement handles indentation itself but we need to do it
            // otherwise.
            bool indentHere = dws.kids.Top is Statement;
            if (indentHere) m_braceCount++;
            GenerateNodeToSB(dws, (SYMBOL) dws.kids.Pop(), sb);
            if (indentHere) m_braceCount--;

            GenerateIndented("while (", dws ,sb);
            GenerateNodeToSB(dws, (SYMBOL) dws.kids.Pop(), sb);
            GenerateLine(");", sb);
        }

        /// <summary>
        /// Generates the code for a ForLoop node.
        /// </summary>
        /// <param name="fl">The ForLoop node.</param>
        /// <returns>String containing C# code for ForLoop fl.</returns>
        private void GenerateForLoop(ForLoop fl, StringBuilder sb)
        {
            GenerateIndented("for (", fl, sb);

            // It's possible that we don't have an assignment, in which case
            // the child will be null and we only print the semicolon.
            // for (x = 0; x < 10; x++)
            //      ^^^^^
            ForLoopStatement s = (ForLoopStatement) fl.kids.Pop();
            if (null != s)
            {
                GenerateForLoopStatement(s, sb);
            }
            Generate("; ", sb);
            // for (x = 0; x < 10; x++)
            //             ^^^^^^
            GenerateNodeToSB(fl, (SYMBOL) fl.kids.Pop(), sb);
            Generate("; ", sb);
            // for (x = 0; x < 10; x++)
            //                     ^^^
            GenerateForLoopStatement((ForLoopStatement) fl.kids.Pop(), sb);
            GenerateLine(")", sb);

            // CompoundStatement handles indentation itself but we need to do it
            // otherwise.
            bool indentHere = fl.kids.Top is Statement;
            if (indentHere) m_braceCount++;
            GenerateNodeToSB(fl, (SYMBOL) fl.kids.Pop(), sb);
            if (indentHere) m_braceCount--;
        }

        /// <summary>
        /// Generates the code for a ForLoopStatement node.
        /// </summary>
        /// <param name="fls">The ForLoopStatement node.</param>
        /// <returns>String containing C# code for ForLoopStatement fls.</returns>
        private void GenerateForLoopStatement(ForLoopStatement fls, StringBuilder sb)
        {
            int comma = fls.kids.Count - 1;  // tells us whether to print a comma

            // It's possible that all we have is an empty Ident, for example:
            //
            //     for (x; x < 10; x++) { ... }
            //
            // Which is illegal in C# (MONO). We'll skip it.
            if (fls.kids.Top is IdentExpression && 1 == fls.kids.Count)
                return;

            for (int i = 0; i < fls.kids.Count; i++)
            {
                SYMBOL s = (SYMBOL)fls.kids[i];

                // Statements surrounded by parentheses in for loops
                //
                // e.g.  for ((i = 0), (j = 7); (i < 10); (++i))
                //
                // are legal in LSL but not in C# so we need to discard the parentheses
                //
                // The following, however, does not appear to be legal in LLS
                //
                // for ((i = 0, j = 7); (i < 10); (++i))
                //
                // As of Friday 20th November 2009, the Linden Lab simulators appear simply never to compile or run this
                // script but with no debug or warnings at all!  Therefore, we won't deal with this yet (which looks
                // like it would be considerably more complicated to handle).
                while (s is ParenthesisExpression)
                    s = (SYMBOL)s.kids.Pop();

                GenerateNodeToSB(fls, s, sb);
                if (0 < comma--)
                    Generate(", ", sb);
            }
        }

        /// <summary>
        /// Generates the code for a BinaryExpression node.
        /// </summary>
        /// <param name="be">The BinaryExpression node.</param>
        /// <returns>String containing C# code for BinaryExpression be.</returns>
        private void GenerateBinaryExpression(BinaryExpression be, StringBuilder sb)
        {
            if (be.ExpressionSymbol.Equals("&&") || be.ExpressionSymbol.Equals("||"))
            {
                // special case handling for logical and/or, see Mantis 3174
                sb.Append("((bool)(");
                GenerateNodeToSB(be, (SYMBOL)be.kids.Pop(), sb);
                sb.Append("))");
                Generate(String.Format(" {0} ", be.ExpressionSymbol.Substring(0,1)), be, sb);
                sb.Append("((bool)(");
                foreach (SYMBOL kid in be.kids)
                    GenerateNodeToSB(be, kid, sb);
                sb.Append("))");
            }
            else
            {
                GenerateNodeToSB(be, (SYMBOL)be.kids.Pop(), sb);
                Generate(String.Format(" {0} ", be.ExpressionSymbol), be, sb);
                foreach (SYMBOL kid in be.kids)
                    GenerateNodeToSB(be, kid, sb);
            }
        }

        /// <summary>
        /// Generates the code for a UnaryExpression node.
        /// </summary>
        /// <param name="ue">The UnaryExpression node.</param>
        /// <returns>String containing C# code for UnaryExpression ue.</returns>
        private void GenerateUnaryExpression(UnaryExpression ue, StringBuilder sb)
        {
            Generate(ue.UnarySymbol, ue, sb);
            GenerateNodeToSB(ue, (SYMBOL) ue.kids.Pop(), sb);
        }

        /// <summary>
        /// Generates the code for a ParenthesisExpression node.
        /// </summary>
        /// <param name="pe">The ParenthesisExpression node.</param>
        /// <returns>String containing C# code for ParenthesisExpression pe.</returns>
        private void GenerateParenthesisExpression(ParenthesisExpression pe, StringBuilder sb)
        {
            string retstr = String.Empty;

            Generate("(", sb);
            foreach (SYMBOL kid in pe.kids)
                GenerateNodeToSB(pe, kid, sb);
            Generate(")", sb);
        }

        /// <summary>
        /// Generates the code for a IncrementDecrementExpression node.
        /// </summary>
        /// <param name="ide">The IncrementDecrementExpression node.</param>
        /// <returns>String containing C# code for IncrementDecrementExpression ide.</returns>
        private void GenerateIncrementDecrementExpression(IncrementDecrementExpression ide, StringBuilder sb)
        {
            if (0 < ide.kids.Count)
            {
                IdentDotExpression dot = (IdentDotExpression) ide.kids.Top;
                Generate(String.Format("{0}", ide.PostOperation ? CheckName(dot.Name) + "." + dot.Member + ide.Operation : ide.Operation + CheckName(dot.Name) + "." + dot.Member), ide, sb);
            }
            else
                Generate(String.Format("{0}", ide.PostOperation ? CheckName(ide.Name) + ide.Operation : ide.Operation + CheckName(ide.Name)), ide, sb);
        }

        /// <summary>
        /// Generates the code for a TypecastExpression node.
        /// </summary>
        /// <param name="te">The TypecastExpression node.</param>
        /// <returns>String containing C# code for TypecastExpression te.</returns>
        private void GenerateTypecastExpression(TypecastExpression te, StringBuilder sb)
        {
            // we wrap all typecasted statements in parentheses
            Generate(String.Format("({0}) (", te.TypecastType), te, sb);
            GenerateNodeToSB(te, (SYMBOL) te.kids.Pop(), sb);
            Generate(")", sb);
        }

        /// <summary>
        /// Generates the code for an identifier
        /// </summary>
        /// <param name="id">The symbol name</param>
        /// <param name="s">The Symbol node.</param>
        /// <returns>String containing C# code for identifier reference.</returns>
        private void GenerateIdentifier(string id, SYMBOL s, StringBuilder  sb)
        {
            if (m_comms != null)
            {
                object value = m_comms.LookupModConstant(id);
                if (value != null)
                {
                    string retval = null;
                    if (value is int)
                        retval = String.Format("new LSL_Types.LSLInteger({0})",((int)value).ToString());
                    else if (value is float)
                        retval = String.Format("new LSL_Types.LSLFloat({0})",((float)value).ToString());
                    else if (value is string)
                        retval = String.Format("new LSL_Types.LSLString(\"{0}\")",((string)value));
                    else if (value is OpenMetaverse.UUID)
                        retval = String.Format("new LSL_Types.key(\"{0}\")",((OpenMetaverse.UUID)value).ToString());
                    else if (value is OpenMetaverse.Vector3)
                        retval = String.Format("new LSL_Types.Vector3(\"{0}\")",((OpenMetaverse.Vector3)value).ToString());
                    else if (value is OpenMetaverse.Quaternion)
                        retval = String.Format("new LSL_Types.Quaternion(\"{0}\")",((OpenMetaverse.Quaternion)value).ToString());
                    else retval = id;

                    Generate(retval, s, sb);
                    return;
                }
            }

            Generate(CheckName(id), s, sb);
            return;
        }

        /// <summary>
        /// Generates the code for a FunctionCall node.
        /// </summary>
        /// <param name="fc">The FunctionCall node.</param>
        /// <returns>String containing C# code for FunctionCall fc.</returns>
        private void GenerateFunctionCall(FunctionCall fc, StringBuilder sb)
        {
            string modinvoke = null;
            if (m_comms != null)
                modinvoke = m_comms.LookupModInvocation(fc.Id);

            if (modinvoke != null)
            {
                if (fc.kids[0] is ArgumentList)
                {
                    if ((fc.kids[0] as ArgumentList).kids.Count == 0)
                        Generate(String.Format("{0}(\"{1}\"",modinvoke,fc.Id), fc, sb);
                    else
                        Generate(String.Format("{0}(\"{1}\",",modinvoke,fc.Id), fc, sb);
                }
            }
            else
            {
                Generate(String.Format("{0}(", CheckName(fc.Id)), fc, sb);
            }

            foreach (SYMBOL kid in fc.kids)
                GenerateNodeToSB(fc, kid, sb);

            Generate(")", sb);
        }

        /// <summary>
        /// Generates the code for a Constant node.
        /// </summary>
        /// <param name="c">The Constant node.</param>
        /// <returns>String containing C# code for Constant c.</returns>
        private void GenerateConstant(Constant c, StringBuilder sb)
        {
            // Supprt LSL's weird acceptance of floats with no trailing digits
            // after the period. Turn float x = 10.; into float x = 10.0;
            if ("LSL_Types.LSLFloat" == c.Type)
            {
                int dotIndex = c.Value.IndexOf('.') + 1;
                if (0 < dotIndex && (dotIndex == c.Value.Length || !Char.IsDigit(c.Value[dotIndex])))
                    c.Value = c.Value.Insert(dotIndex, "0");
                c.Value = "new LSL_Types.LSLFloat("+c.Value+")";
            }
            else if ("LSL_Types.LSLInteger" == c.Type)
            {
                c.Value = "new LSL_Types.LSLInteger("+c.Value+")";
            }
            else if ("LSL_Types.LSLString" == c.Type)
            {
                c.Value = "new LSL_Types.LSLString(\""+c.Value+"\")";
            }

            Generate(c.Value, c, sb);
        }

        /// <summary>
        /// Generates the code for a VectorConstant node.
        /// </summary>
        /// <param name="vc">The VectorConstant node.</param>
        /// <returns>String containing C# code for VectorConstant vc.</returns>
        private void GenerateVectorConstant(VectorConstant vc, StringBuilder sb)
        {
            Generate(String.Format("new {0}(", vc.Type), vc, sb);
            GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
            Generate(", ", sb);
            GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
            Generate(", ", sb);
            GenerateNodeToSB(vc, (SYMBOL) vc.kids.Pop(), sb);
            Generate(")", sb);
        }

        /// <summary>
        /// Generates the code for a RotationConstant node.
        /// </summary>
        /// <param name="rc">The RotationConstant node.</param>
        /// <returns>String containing C# code for RotationConstant rc.</returns>
        private void GenerateRotationConstant(RotationConstant rc, StringBuilder sb)
        {
            Generate(String.Format("new {0}(", rc.Type), rc, sb);
            GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
            Generate(", ", sb);
            GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
            Generate(", ", sb);
            GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
            Generate(", ", sb);
            GenerateNodeToSB(rc, (SYMBOL) rc.kids.Pop(), sb);
            Generate(")", sb);
        }

        /// <summary>
        /// Generates the code for a ListConstant node.
        /// </summary>
        /// <param name="lc">The ListConstant node.</param>
        /// <returns>String containing C# code for ListConstant lc.</returns>
        private void GenerateListConstant(ListConstant lc, StringBuilder sb)
        {
            Generate(String.Format("new {0}(", lc.Type), lc, sb);

            foreach (SYMBOL kid in lc.kids)
                GenerateNodeToSB(lc, kid, sb);

            Generate(")", sb);
        }

        /// <summary>
        /// Prints a newline.
        /// </summary>
        /// <returns>A newline.</returns>
        private void GenerateLine(StringBuilder sb)
        {
            sb.Append("\n");
            m_CSharpLine++;
            m_CSharpCol = 1;
        }

        /// <summary>
        /// Prints text, followed by a newline.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <returns>String s followed by newline.</returns>
        private void GenerateLine(string s, StringBuilder sb)
        {
            sb.Append(s);
            sb.Append("\n");
            m_CSharpLine++;
            m_CSharpCol = 1;
        }

        /// <summary>
        /// Prints text, followed by a newline.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <param name="sym">Symbol being generated to extract original line
        /// number and column from.</param>
        /// <returns>String s followed by newline.</returns>
        private void GenerateLine(string s, SYMBOL sym, StringBuilder sb)
        {
            Generate(s, sym, sb);
            sb.Append("\n");

            m_CSharpLine++;
            m_CSharpCol = 1;
        }

        /// <summary>
        /// Prints text.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <returns>String s.</returns>
        private void Generate(string s, StringBuilder sb)
        {
            sb.Append(s);
            m_CSharpCol += s.Length;
        }

        /// <summary>
        /// Prints text.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <param name="sym">Symbol being generated to extract original line
        /// number and column from.</param>
        /// <returns>String s.</returns>
        private void Generate(string s, SYMBOL sym, StringBuilder sb)
        {
            sb.Append(s);
            if (null != sym)
                m_positionMap.Add(new KeyValuePair<int, int>(m_CSharpLine, m_CSharpCol), new KeyValuePair<int, int>(sym.Line, sym.Position));

            m_CSharpCol += s.Length;
        }

        /// <summary>
        /// Prints text correctly indented, followed by a newline.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <returns>Properly indented string s followed by newline.</returns>
        private void GenerateIndentedLine(string s, StringBuilder sb)
        {
            GenerateIndentedLine(s, null, sb);
        }

        /// <summary>
        /// Prints text correctly indented, followed by a newline.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <param name="sym">Symbol being generated to extract original line
        /// number and column from.</param>
        /// <returns>Properly indented string s followed by newline.</returns>
        private void GenerateIndentedLine(string s, SYMBOL sym, StringBuilder sb)
        {
            GenerateIndented(s, sym , sb );
            sb.Append("\n");
            m_CSharpLine++;
            m_CSharpCol = 1;
        }

        /// <summary>
        /// Prints text correctly indented.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <returns>Properly indented string s.</returns>
        //private string GenerateIndented(string s)
        //{
        //    return GenerateIndented(s, null);
        //}
        // THIS FUNCTION IS COMMENTED OUT TO SUPPRESS WARNINGS

        /// <summary>
        /// Prints text correctly indented.
        /// </summary>
        /// <param name="s">String of text to print.</param>
        /// <param name="sym">Symbol being generated to extract original line
        /// number and column from.</param>
        /// <returns>Properly indented string s.</returns>
        private void GenerateIndented(string s, SYMBOL sym, StringBuilder sb)
        {
            Indent(sb);
            sb.Append(s);

            if (null != sym)
                m_positionMap.Add(new KeyValuePair<int, int>(m_CSharpLine, m_CSharpCol), new KeyValuePair<int, int>(sym.Line, sym.Position));

            m_CSharpCol += s.Length;
        }

        /// <summary>
        /// Prints correct indentation.
        /// </summary>
        /// <returns>Indentation based on brace count.</returns>
        private void Indent(StringBuilder sb)
        {
            for (int i = 0; i < m_braceCount; i++)
            {
                sb.Append("    ");
                m_CSharpCol += 4;
            }
        }

        /// <summary>
        /// Returns the passed name with an underscore prepended if that name is a reserved word in C#
        /// and not resevered in LSL otherwise it just returns the passed name.
        ///
        /// This makes no attempt to cache the results to minimise future lookups. For a non trivial
        /// scripts the number of unique identifiers could easily grow to the size of the reserved word
        /// list so maintaining a list or dictionary and doing the lookup there firstwould probably not
        /// give any real speed advantage.
        ///
        /// I believe there is a class Microsoft.CSharp.CSharpCodeProvider that has a function
        /// CreateValidIdentifier(str) that will return either the value of str if it is not a C#
        /// key word or "_"+str if it is. But availability under Mono?
        /// </summary>
        private string CheckName(string s)
        {
            if (CSReservedWords.IsReservedWord(s))
                return "@" + s;
            else
                return s;
        }
    }
}
