/
etc/
lib/
src/Abilities/
src/Abilities/Skills/
src/Abilities/Spells/
src/Abilities/Spells/Enums/
src/Affects/
src/ArtheaConsole/
src/ArtheaConsole/Properties/
src/ArtheaGUI/Properties/
src/Clans/Enums/
src/Commands/Communication/
src/Commands/ItemCommands/
src/Connections/
src/Connections/Colors/
src/Connections/Enums/
src/Connections/Players/
src/Connections/Players/Enums/
src/Continents/
src/Continents/Areas/
src/Continents/Areas/Characters/
src/Continents/Areas/Characters/Enums/
src/Continents/Areas/Items/
src/Continents/Areas/Items/Enums/
src/Continents/Areas/Rooms/
src/Continents/Areas/Rooms/Enums/
src/Continents/Areas/Rooms/Exits/
src/Creation/
src/Creation/Attributes/
src/Creation/Interfaces/
src/Database/
src/Database/Interfaces/
src/Environment/
src/Properties/
src/Scripts/Enums/
src/Scripts/Interfaces/
#region Arthea License

/***********************************************************************
*  Arthea MUD by R. Jennings (2007)      http://arthea.googlecode.com/ *
*  By using this code you comply with the Artistic and GPLv2 Licenses. *
***********************************************************************/

#endregion

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Xml.Serialization;
using Arthea.Continents.Areas.Characters;
using Arthea.Continents.Areas.Items;
using Arthea.Creation;
using Arthea.Environment;
using Arthea.Interfaces;
using Arthea.Scripts.Enums;

namespace Arthea.Scripts
{
    /// <summary>
    /// Implementation of a script.
    /// </summary>
    [Serializable]
    [XmlInclude(typeof (CharScript))]
    [XmlInclude(typeof (ItemScript))]
    [XmlInclude(typeof (RoomScript))]
    public abstract class Script
    {
        #region [rgn] Fields (10)

        private const int beginBlock = 0;
        private const int endBlock = -2;
        private const int inBlock = -1;
        private const int maxCallLevel = 5;
        private const int maxNestedLevel = 12;
        private static readonly Dictionary<string, OperatorType> operators = new Dictionary<string, OperatorType>();
        private static int callLevel = 0;
        private ScriptCode code;
        private string trigger;
        private TriggerType type;

        #endregion [rgn]

        #region [rgn] Constructors (3)

        // initialize operand list
        static Script()
        {
            operators.Add("<=", OperatorType.LessThanOrEqualTo);
            operators.Add("<", OperatorType.LessThan);
            operators.Add("==", OperatorType.EqualTo);
            operators.Add("=", OperatorType.EqualTo);
            operators.Add(">", OperatorType.GreaterThan);
            operators.Add(">=", OperatorType.GreaterThanOrEqualTo);
            operators.Add("!=", OperatorType.NotEqualTo);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Script"/> class.
        /// </summary>
        /// <param name="code">The code.</param>
        /// <param name="trigger">The trigger.</param>
        public Script(ScriptCode code, string trigger)
        {
            this.code = code;
            this.trigger = trigger;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Script"/> class.
        /// </summary>
        public Script()
        {
        }

        #endregion [rgn]

        #region [rgn] Properties (3)

        /// <summary>
        /// Gets or sets the code.
        /// </summary>
        /// <value>The code.</value>
        public ScriptCode Code
        {
            get { return code; }
            set { code = value; }
        }

        /// <summary>
        /// Gets or sets the trigger.
        /// </summary>
        /// <value>The trigger.</value>
        public string Trigger
        {
            get { return trigger; }
            set { trigger = value; }
        }

        /// <summary>
        /// Gets or sets the type.
        /// </summary>
        /// <value>The type.</value>
        public TriggerType Type
        {
            get { return type; }
            set { type = value; }
        }

        #endregion [rgn]

        #region [rgn] Methods (6)

        // [rgn] Public Methods (1)

        /// <summary>
        /// Interprets the script code.
        /// </summary>
        /// <param name="from">The object initiating the script.</param>
        /// <param name="ch">The character who triggered the script.</param>
        /// <param name="arg1">The arg1.</param>
        /// <param name="arg2">The arg2.</param>
        public void Interpret(Scriptable from, Character ch, object arg1, object arg2)
        {
            if (string.IsNullOrEmpty(code.Text))
                return;

            string[] lines = code.Text.Split(new string[] {System.Environment.NewLine},
                                             StringSplitOptions.RemoveEmptyEntries);

            int[] state = new int[maxNestedLevel];
            bool[] cond = new bool[maxNestedLevel];
            int level = 0;

            if (++callLevel > maxCallLevel)
            {
                Log.Bug("Script.Interpret: Max call level exceeded. (script {0})", code.Id);
                return;
            }

            foreach (String line in lines)
            {
                // skip comments
                if (line.Substring(0, 2) == "//")
                    continue;

                String ctrl = line.FirstArg();

                switch (ctrl)
                {
                    case "if":
                        if (state[level] == beginBlock)
                        {
                            Log.Bug("Script.Interpret: misplaced if statement (script {0})", code.Id);
                            callLevel--;
                            return;
                        }

                        state[level] = beginBlock;

                        if (++level > maxCallLevel)
                        {
                            Log.Bug("Script.Interpret: max call level exceeded (script {0})", code.Id);
                            callLevel--;
                            return;
                        }

                        if (level > 0 && cond[level - 1] == false)
                        {
                            cond[level] = false;
                            continue;
                        }

                        try
                        {
                            cond[level] = CheckCondition(from, ch, line, arg1, arg2);
                        }
                        catch
                        {
                            Log.Bug("Script.Interpret: Invalid if check (script {0})", code.Id);
                            callLevel--;
                            return;
                        }

                        state[level] = endBlock;
                        break;
                    case "or":
                        if (level == 0 || state[level - 1] != beginBlock)
                        {
                            Log.Bug("Script.Interpret: or without if (script {0})", code.Id);
                            return;
                        }
                        if (level > 0 && cond[level - 1] == false)
                            continue;

                        try
                        {
                            cond[level] = (CheckCondition(from, ch, line, arg1, arg2)) ? true : cond[level];
                        }
                        catch
                        {
                            Log.Bug("Script.Interpret: Invalid or check (script {0})", code.Id);
                            callLevel--;
                            return;
                        }
                        break;
                    case "and":
                        if (level == 0 || state[level - 1] != beginBlock)
                        {
                            Log.Bug("Script.Interpret: and without if (script {0})", code.Id);
                            return;
                        }
                        if (level > 0 && cond[level - 1] == false)
                            continue;

                        try
                        {
                            cond[level] = (cond[level] && CheckCondition(from, ch, line, arg1, arg2))
                                              ? true
                                              : cond[level];
                        }
                        catch
                        {
                            Log.Bug("Script.Interpret: Invalid or check (script {0})", code.Id);
                            callLevel--;
                            return;
                        }
                        break;
                    case "endif":
                        if (level == 0 || state[level - 1] != beginBlock)
                        {
                            Log.Bug("Script.Interpret: endif without if (script {0})", code.Id);
                            return;
                        }
                        cond[level] = true;
                        state[level] = inBlock;
                        state[--level] = endBlock;
                        break;
                    case "else":
                        if (level == 0 || state[level - 1] != beginBlock)
                        {
                            Log.Bug("Script.Interpret: else without if (script {0})", code.Id);
                            return;
                        }
                        if (level > 0 && cond[level - 1] == false)
                            continue;
                        state[level] = inBlock;
                        cond[level] = (cond[level] ? false : true);
                        break;
                    case "end":
                    case "break":
                        if (cond[level])
                            callLevel--;
                        return;
                    default:
                        if (level == 0 || cond[level])
                        {
                            state[level] = inBlock;

                            // expand args
                            String args = ExpandArgs(from, ch, line, arg1, arg2);

                            // process command
                            if (!InterpretCommand(from, ctrl, args))
                            {
                                Log.Bug("Unknown command '{0}' (script {1})", ctrl, code.Id);
                            }
                        }
                        break;
                }
            }
        }

        // [rgn] Protected Methods (1)

        /// <summary>
        /// Interprets a script command.
        /// </summary>
        /// <param name="from">The object with the script.</param>
        /// <param name="cmd">The command (method name).</param>
        /// <param name="argument">The argument.</param>
        /// <returns>true if a command was interpreted</returns>
        protected abstract bool InterpretCommand(Scriptable from, String cmd, String argument);

        // [rgn] Private Methods (4)

        // evalute args in a line and compare them
        private bool CheckCondition(Scriptable from, Character ch, String line, object arg1, object arg2)
        {
            String[] values = line.GetArgs();

            switch (values.Length)
            {
                default:
                    Log.Bug("Invalid if check (script {0})", code.Id);
                    throw new Exception();
                case 1: // a boolean value
                    return GetOperand(from, ch, values[0], arg1, arg2) != null;
                case 3: // compare two operands
                    return Operate(GetOperand(from, ch, values[0], arg1, arg2),
                                   operators[values[1]],
                                   GetOperand(from, ch, values[2], arg1, arg2));
            }
        }

        private string ExpandArgs(Scriptable from, Scriptable ch, string format, object arg1, object arg2)
        {
            StringBuilder buf = new StringBuilder();
            for (CharEnumerator c = format.GetEnumerator(); c.MoveNext();)
            {
                if (c.Current != '$')
                {
                    buf.Append(c.Current);
                    continue;
                }

                if (!c.MoveNext())
                    break;

                switch (c.Current)
                {
                    default:
                        Log.Bug("Bad argument ${0} (script {1}", c.Current, code.Id);
                        break;
                    case 'i':
                        buf.Append(new String(from.Name).FirstArg());
                        break;
                    case 'I':
                        buf.Append(from.ShortDescr);
                        break;
                    case 'n':
                        buf.Append(new String(ch.Name).FirstArg());
                        break;
                    case 'N':
                        buf.Append(ch.ShortDescr);
                        break;
                    case 't':
                        if (arg2 is Character)
                            buf.Append(new String((arg2 as Character).Name).FirstArg());
                        break;
                    case 'T':
                        if (arg2 is Character)
                            buf.Append((arg2 as Character).ShortDescr);
                        break;
                    case 'o':
                        if (arg1 is Item)
                            buf.Append(new String((arg1 as Item).Name).FirstArg());
                        break;
                    case 'O':
                        if (arg1 is Item)
                            buf.Append((arg1 as Item).ShortDescr);
                        break;
                    case 'p':
                        if (arg2 is Item)
                            buf.Append(new String((arg2 as Item).Name).FirstArg());
                        break;
                    case 'P':
                        if (arg2 is Item)
                            buf.Append((arg2 as Item).ShortDescr);
                        break;
                }
            }

            return buf.ToString();
        }

        private static object GetOperand(Scriptable from, Character ch, string line, object arg1, object arg2)
        {
            object operand;

            // check for an expanded arg value
            if (line[0] == '$')
            {
                // get the base object
                switch (char.ToLower(line[1]))
                {
                    case 'i':
                        operand = from;
                        break;
                    case 'n':
                        operand = ch;
                        break;
                    case 't':
                    case 'p':
                        operand = arg2;
                        break;
                    case 'o':
                        operand = arg1;
                        break;
                    default:
                        throw new Exception();
                }

                // ok now parse lines like:  $n.clan.name
                string[] ops = line.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);

                for (int i = 1; i < ops.Length; i++)
                {
                    FieldInfo info = Olc.GetField(operand.GetType(), ops[i]);

                    if (info != null)
                    {
                        operand = info.GetValue(operand);
                    }
                }
            }
                // else check for implemented conditional keywords
            else
            {
                ConditionKeyword condition;

                try
                {
                    condition = (ConditionKeyword) Enum.Parse(typeof (ConditionKeyword), line, true);
                }
                catch
                {
                    // no implemented keyword, pass the line through for comparing
                    return line;
                }

                switch (condition)
                {
                    case ConditionKeyword.Random:
                        operand = Randomizer.Next(1, 100);
                        break;
                    default:
                        throw new Exception();
                }
            }
            return operand;
        }

        // compare two values based on given operator
        private bool Operate(object lvalue, OperatorType oper, object rvalue)
        {
            switch (oper)
            {
                case OperatorType.EqualTo:
                    return lvalue == rvalue;
                case OperatorType.NotEqualTo:
                    return lvalue != rvalue;
                case OperatorType.GreaterThan:
                    return lvalue.ToString().CompareTo(rvalue) > 0;
                case OperatorType.GreaterThanOrEqualTo:
                    return lvalue.ToString().CompareTo(rvalue) >= 0;
                case OperatorType.LessThan:
                    return lvalue.ToString().CompareTo(rvalue) < 0;
                case OperatorType.LessThanOrEqualTo:
                    return lvalue.ToString().CompareTo(rvalue) <= 0;
                default:
                    Log.Bug("Unknown operator (script {0})", code.Id);
                    return false;
            }
        }

        #endregion [rgn]
    }
}