/// Runtime Debugger tool
/// see class description. 
///
/// Jonathan Smårs © jsmars.com
///
/// Xen has seperate more efficient drawing and supports colors in fonts. Should use jsmars modified version
/// use preprocessor 'Xen' to enable xen drawing

#region Usings

using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Globalization;
using Microsoft.Win32;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Storage;
#if XEN // color coding using #0055FF or #05F for text only supported for Xen
using Xen;
using Xen.Graphics;
using Xen.Ex.Graphics2D;
#endif
#if TOOLS
using Tools;
using System.Reflection;
#endif

#endregion

/// <summary>
/// <para>Ward is a runtime debugging class, used to keep track of and view debugging information at runtime</para>
/// <para>You can see key input, create fields, counters, log messages, timers with graphs, graphs, and see various lists</para>
/// <para>Jonathan Smårs © jsmars.com</para>
/// <para></para>
/// <para>Usage:</para>
/// <para>* Ward must have DEBUG preprocessor defined in your project, or it's methods will be empty //TODO: Many new methods may still be active</para>
/// <para>* Creation: </para>
/// <para>     Spritebatch: Add a Ward.SpritebatchComponent to your components</para>
/// <para>     Xen: Add a Ward.XenComponent to your components</para>
/// <para>* Bind your keyinput.OnCharEntered of choice to Ward.OnCharEntered</para>
/// </summary>
public static class Ward
{
    #region Public Fields

#if XEN
    public static XenComponent InstanceXenComponent { get; private set; }
#endif
    public static SpritebatchComponent InstanceSpritebatchComponent { get; private set; }
    public static int ItemCount = Enum.GetValues(typeof(Items)).Length;

    public static void SetVisible(Items item, bool visible) { ItemVisible[(int)item] = visible; regKey.SetValue(item.ToString(), visible); if (item == Items.Ward && visible) active = true; }
    public static bool Visible { get { return ItemVisible[0]; } set { ItemVisible[0] = true; if (ItemVisible[0]) active = true; } }
    public static bool Active { get { return active; } set { active = value; } }
    public static String InputText = "";
    public static float InputNumeric { get { float f; if (float.TryParse(InputText, out f) || float.TryParse(InputText.Replace('.',','), out f)) return f; return 0; } }
    public static Texture2D Pixel { get; private set; }
    public static float LineThickness = 1;
    public static float PointSize = 3;
    public static int LogViewLength = 20;

    public static LogEvent OnLog;
    public static string RegistryPath { get; private set; }

    #endregion

    #region Private Fields
#if DEBUG

    internal static bool Initialized { get; private set; }
    internal static RegistryKey regKeyWatches;

    private static bool active = false;
    private static string fontPath;
    private const Keys FunctionKey = Keys.F12, WardDisable = Keys.F1; //TODO: ResetCounterAvg key
    private static Keys[] ItemKeys = new Keys[] 
    {
        Keys.F1, 
        Keys.F2, 
        Keys.F3, 
        Keys.F4, 
        Keys.F5, 
        Keys.F6, 
        Keys.F7, 
        Keys.F8, 
        Keys.F9,
        Keys.F10,
        //Keys.F11,
    };
    private static string fs = FunctionKey + "+";
    private static string[] ItemStr = new string[] 
    { 
        "WARD || Disable: " + WardDisable + " || Hide: " + ItemKeys[(int)Items.Ward] + " || Objects: " + fs + ItemKeys[(int)Items.WardObjects],
        "Watches (" + fs + ItemKeys[(int)Items.Watch] + "): ",
        "Counters Total/AvgFrame/Frame (" + fs + ItemKeys[(int)Items.Counters] + "): ",
        "Log (" + fs + ItemKeys[(int)Items.Log] + "): ",
        "Timers (" + fs + ItemKeys[(int)Items.Timers] + "): ",
        "Memory (" + fs + ItemKeys[(int)Items.Memory] + "): ",
        "Load & Threading (" + fs + ItemKeys[(int)Items.LoadingThreading] + "): ",
        "World Info (" + fs + ItemKeys[(int)Items.World] + ")",
        "Ward Objects (" + fs + ItemKeys[(int)Items.WardObjects] + ")",
        "Network Objects (" + fs + ItemKeys[(int)Items.NetworkObj] + "): ",
    };
    private static bool[] ItemVisible = new bool[ItemCount];
    private static int[] LastItemCounts = new int[ItemCount];
#if XEN
    private static TextElementRect[] ItemStringbars = new TextElementRect[ItemCount];
    private static Vector2 itemStringBarDefaultSize = new Vector2(1200, 20);
#else
    private static Vector2 itemStringBarDefaultSize = new Vector2(500, 20);
#endif
    private static List<string> watchHides = new List<string>(), watchShows = new List<string>();

 
    private const int LineSpacing = 4;
    private const int ItemSpacing = 20;
    private const string Indent = "    ";
    private const int IndentWidth = 50;

    private static List<LogMsg> log = new List<LogMsg>();
    private static Dictionary<string, bool> logDupes = new Dictionary<string, bool>();
    private static StringBuilder Sb = new StringBuilder();

    private static Dictionary<string, xWatch> Watches = new Dictionary<string, xWatch>();
    private static Dictionary<string, TwoInts> Counters = new Dictionary<string, TwoInts>();
    private static List<xWatch> WatchesSorted = new List<xWatch>();
    private static List<string> CountersSorted = new List<string>();
    private static Dictionary<string, LineLabeled> Lines = new Dictionary<string, LineLabeled>();
    private static Dictionary<string, Line3DLabeled> Lines3D = new Dictionary<string, Line3DLabeled>();
    private static Dictionary<string, BoxLabeled> Boxes = new Dictionary<string, BoxLabeled>();
    private static Dictionary<string, PointLabeled> Points = new Dictionary<string, PointLabeled>();
    private static Dictionary<string, Point3DLabeled> Points3D = new Dictionary<string, Point3DLabeled>();
    private static Dictionary<string, TimerGraph> Timers = new Dictionary<string, TimerGraph>();

    // NetObjList
    public static ICollection NetObjList;
    private static int NetObjDrawLen = 20;
    private static int NetObjScroll = 1;
    private static int lastNetObjCount;
    private static bool netUptNext;

    private static Matrix view3D = Matrix.Identity, proj3D = Matrix.Identity, view2D = Matrix.Identity;

    private static float fontHeight = 15;
    private static SpriteFont font;
    //private static SpriteBatch spritebatch = null;
    private static GraphicsDevice graphics = null;
    //private static ContentManager content = null;
    private static RegistryKey regKey;

    private static TimeSpan InputReset = TimeSpan.FromSeconds(1);
    private static DateTime lastInput;

    private static int sliderWidth = 200;

    private static Random rnd = new Random();

    // Editing Watch Values
    private static Keys editUp = Keys.Up;
    private static Keys editDown = Keys.Down;
    private static Keys editIncr = Keys.Right;
    private static Keys editDecr = Keys.Left;

    // FPS Counter
    private static int fpsLast, fpsLastD;
    private static int fpsCounter, fpsCounterD;
    private static DateTime fpsLastUpdate;

#endif
    #endregion

    #region Input

    private static Point mouseStart;
    private static float lastWatchLeftBot;
    private static int selWatch;

    private static void handleInput()
    {
        if (keyPressed(ItemKeys[0]) && !keyDown(FunctionKey))
            SetVisible(Items.Ward, !ItemVisible[0]);

        if (Visible)
        {
            if (keyDown(FunctionKey))
            {
                // Toggle Visibility of ward items
                for (int i = 1; i < ItemCount; i++)
                    if (keyPressed(ItemKeys[i]))
                        SetVisible((Items)i, !ItemVisible[i]);

                // Hide Ward
                if (keyPressed(WardDisable))
                {
                    Active = false;
                    SetVisible(Items.Ward, false);
                }

                #region Scroll net object list

                if (NetObjList != null && NetObjList.Count > NetObjDrawLen)
                {
                    if (ms.ScrollWheelValue != msPrev.ScrollWheelValue)
                    {
                        NetObjScroll += (int)((ms.ScrollWheelValue - msPrev.ScrollWheelValue) / 400f * NetObjDrawLen);
                        netUptNext = true;
                    }

                    if (lastNetObjCount < NetObjDrawLen || (lastNetObjCount != NetObjList.Count && NetObjScroll == lastNetObjCount - NetObjDrawLen))
                        NetObjScroll = NetObjList.Count - NetObjDrawLen;


                    NetObjScroll = (int)MathHelper.Clamp(NetObjScroll, 0, NetObjList.Count - NetObjDrawLen);

                    lastNetObjCount = NetObjList.Count;
                }

                #endregion

                #region Edit Watch Values using keys

                // Find first/next editable value when:
                if (keyPressed(editDown) || keyPressed(editUp) //Keys are pressed
                    || xWatch.editIndex < 0 // No values is set
                    || xWatch.editIndex >= WatchesSorted.Count
                    || WatchesSorted.Count <= xWatch.editIndex && WatchesSorted[xWatch.editIndex].Editable) // Invalid value is set
                    for (int i = 0; i < WatchesSorted.Count; i++)
                    {
                        int index = xWatch.editIndex + (keyPressed(editUp) ? i * -1 : i); // move in the right direction
                        if (index < 0) index += WatchesSorted.Count; // cycle list when too small
                        index %= WatchesSorted.Count;  // modulus to cycle list when too large

                        // find watch with sorted index
                        xWatch w = WatchesSorted[index];
                        //foreach (var item in Watches)
                        //    if (item.Value.SortedIndex == index)
                        //    {
                        //        w = item.Value;
                        //        break;
                        //    }

                        // see if we use this value
                        if (w != null && w.Editable && w.SortedIndex != xWatch.editIndex)
                        {
                            xWatch.editIndex = index;
                            break;
                        }
                    }

                // Change value if keys are pressed
                if (xWatch.editIndex >= 0 && xWatch.editIndex < Watches.Count)
                {
                    xWatch wa = WatchesSorted[xWatch.editIndex];
                    if (keyDown(editIncr))
                        wa.EditAdd();
                    if (keyDown(editDecr))
                        wa.EditSubtract();
                }

                #endregion
            }

            #region Edit Watch values using mouse

            if (ms.LeftButton == ButtonState.Pressed)
            {
                if (msPrev.LeftButton == ButtonState.Released)
                {
                    if (ms.X < 400)
                        mouseStart = new Point(ms.X, ms.Y);
                    else
                        mouseStart = Point.Zero;
                }
                else
                {
                    float f = mouseStart.Y - lastWatchLeftBot - fontHeight * 2 - 1;
                    f /= fontHeight;
                    int wIndex = (int)f;

                    if (wIndex >= 0 && wIndex < WatchesSorted.Count)
                    {
                        var w = WatchesSorted.ElementAt(wIndex);
                        if (w.Editable)
                        {
                            int iterations = ms.X - mouseStart.X;
                            if (iterations > 0)
                                for (int i = 0; i < iterations; i++)
                                    w.EditAdd();
                            else
                                for (int i = 0; i < -iterations; i++)
                                    w.EditSubtract();
                            mouseStart.X = ms.X;
                        }
                    }
                }
            }

            #endregion

            // Clear input text
            if (keyPressed(Keys.Delete))
                InputText = "";
        }
    }

    #endregion

    #region Initialize

    /// <summary>
    /// Use this if you want to initialize Ward yourself, possibly before creating the instances so that you can use static watches.
    /// <para>Will be called automatically when components are created if it has not been called already.</para>
    /// <para>returns true, simply so that you can place it as a static varaible if you wish to call it immediately</para>
    /// </summary>
    public static bool Initialize(string registryPath = "Software\\jsmars\\Debug\\")
    {
        if (Initialized) return true;
        if (string.IsNullOrEmpty(registryPath)) throw new ArgumentNullException("registryPath");

        RegistryPath = registryPath;
        regKeyWatches = Registry.CurrentUser.CreateSubKey(registryPath);

        if (ItemVisible[0])
            active = true;

        regKey = Registry.CurrentUser.CreateSubKey("Software\\jsmars\\Ward\\");
        for (int i = 0; i < ItemCount; i++)
            ItemVisible[i] = Convert.ToBoolean(regKey.GetValue(((Items)i).ToString(), i == 0 ? false : true));

        var r = regKey.GetValue("watchHides");
        if (r != null)
        {
            var s = r.ToString().Split(' ');
            for (int i = 0; i < s.Length; i++)
                if (s[i].Length > 0)
                    watchHides.Add(s[i]);
        }
        r = regKey.GetValue("watchShows");
        if (r != null)
        {
            var s = r.ToString().Split(' ');
            for (int i = 0; i < s.Length; i++)
                if (s[i].Length > 0)
                    watchShows.Add(s[i]);
        }

        // Load any watches from reg which were created prior to initialization
        foreach (var item in Watches)
            item.Value.TryLoadReg();

        Initialized = true;
        return true;
    }

    #endregion

    #region Components

    #region Base Component

    private static Timer wtUpd, wtDraw;

    private static void compConstructor(GraphicsDevice gd, string FontPath, string registryPath)
    {
#if XEN
        if (InstanceXenComponent != null) throw new Exception("Instance of XenComponent already created!");
#endif
        if (InstanceSpritebatchComponent != null) throw new Exception("Instance of SpritebatchComponent already created!");
        if (string.IsNullOrEmpty(FontPath)) throw new ArgumentNullException("FontPath");

        if (gd == null) throw new ArgumentNullException("gd");
        graphics = gd;
        fontPath = FontPath;

        if (!Ward.Initialized)
            Initialize(registryPath);
    }

    private static void compInitialize()
    {
        //if (!File.Exists(Path.Combine(content.RootDirectory, FontPath)))
        //    throw new Exception("Create a font at Content/" + FontPath + " or change Ward.FontPath before Initiating. (Choose a small font)");

        Pixel = new Texture2D(graphics, 1, 1);
        Color[] color = new Color[] { Color.White };
        Pixel.SetData<Color>(color);

        SetGraph("Component Update", 16, 150, 80, false, Color.Transparent);
        SetGraph("Component Draw", 16, 150, 80, false, Color.Transparent);

        wtUpd = CreateTimer("Component Update", "Ward", 0);
        wtDraw = CreateTimer("Component Draw", "Ward", 0);
    }

    private static void compLoadContent()
    {
        if (font == null) throw new ArgumentNullException("font");

        fontHeight = (int)((font.MeasureString("Ij\nIj").Y) - (font.MeasureString("Ij").Y));
    }

    private static void compUpdate(float totalTimeSeconds)
    {
#if DEBUG
        if (active)
            wtUpd.Start();

        ks = Keyboard.GetState();
        ms = Mouse.GetState();

        handleInput();

        if (active)
        {
            #region FPS

            if (DateTime.Now - fpsLastUpdate >= TimeSpan.FromSeconds(1))
            {
                fpsLast = fpsCounter;
                fpsLastD = fpsCounterD;
                fpsLastUpdate = DateTime.Now;
                fpsCounter = fpsCounterD = 0;
            }
            fpsCounter++;

            #endregion

            if (ItemVisible[(int)Items.Counters])
            {
                //foreach (var item in Counters)
                //    item.Value.Two = 0;

                CountersSorted.Clear();
                foreach (var item in Counters)
                    CountersSorted.Add(string.Concat(item.Key, " : ",
                                        item.Value.One, " / ",
                                        (item.Value.One / ((float)totalTimeSeconds) * 0.0166667).ToString("#####0.00"), " / ",
                                        item.Value.Two));
                sortCounters();
            }

            if (ItemVisible[(int)Items.Watch])
            {
                // Update Watch Delegates
                foreach (var item in Watches)
                    item.Value.UpdateValue();
                sortWatches();
            }

            if (ItemVisible[(int)Items.Timers])
            {
                foreach (var item in Timers)
                    item.Value.Update();
            }
        }

        ksPrev = ks;
        msPrev = ms;
        if (active)
            wtUpd.Stop();
#endif
    }

    private static void sortCounters()
    {
        CountersSorted.Sort((a, b) => { return a.CompareTo(b); });
    }
    private static void sortWatches()
    {
        WatchesSorted = Watches.Values.ToList();
        for (int i = WatchesSorted.Count - 1; i >= 0; i--)
            if (!WatchesSorted[i].Visible)
            {
                WatchesSorted[i].SortedIndex = -1;
                WatchesSorted.RemoveAt(i);
            }
        WatchesSorted.Sort((a, b) => { return a.Name.CompareTo(b.Name); });
        for (int i = 0; i < WatchesSorted.Count; i++)
            WatchesSorted[i].SortedIndex = i;
    }

    #endregion

    #region XenComponent
#if XEN

    public class XenComponent : IComponent, IDraw, IContentOwner
    {
        public XenComponent(GraphicsDevice gd, string fontPath = "Fonts/default", string registryPath = "Software\\jsmars\\Debug\\")
        {
            compConstructor(gd, fontPath, registryPath);
            InstanceXenComponent = this;
        }

        public void Initialize()
        {
            compInitialize();

            for (int i = 0; i < ItemStringbars.Length; i++)
                ItemStringbars[i] = new TextElementRect(itemStringBarDefaultSize) { Colour = Color.White, ReadHexColors = true, DrawShadow = true };
        }

        public void LoadContent(ContentState state)
        {
            try { font = state.Load<SpriteFont>(fontPath); }
            catch (Exception e) { throw new Exception("\nCreate a font at Content/" + fontPath + ". (Choose a small font)"); }

            foreach (var item in ItemStringbars)
                item.Font = font;

            compLoadContent();
        }

        public UpdateFrequency Update(UpdateState state)
        {
            compUpdate(state.TotalTimeSeconds);
            return UpdateFrequency.OncePerFrame;
        }

        public void Draw(DrawState state)
        {
            drawWard(null, state);
        }

        public bool CullTest(ICuller culler)
        {
            return true;
        }
    }

#endif
    #endregion

    #region SpritebatchComponent

    public class SpritebatchComponent : DrawableGameComponent
    {
        SpriteBatch sb;

        public SpritebatchComponent(Game game, SpriteBatch spritebatch, GraphicsDevice gd, string fontPath = "Common/Fonts/default", string registryPath = "Software/jsmars/Debug/") 
            : base(game)
        {
            compConstructor(gd, fontPath, registryPath);
            sb = spritebatch;
        }

        public override void Initialize()
        {
            compInitialize();
            base.Initialize();
        }

        protected override void LoadContent()
        {
            try { font = Game.Content.Load<SpriteFont>(fontPath); }
            catch (Exception e) { throw new Exception("\nCreate a font at [Content folder]/" + fontPath + " or change font WARD font directory. (Choose a small font)"); }

            compLoadContent();
            base.LoadContent();
        }

        public override void Update(GameTime gameTime)
        {
            compUpdate((float)gameTime.TotalGameTime.TotalSeconds);
            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            drawWard(sb
                        #if XEN
                    , null
                        #endif
                    );
            base.Draw(gameTime);
        }
    }

    #endregion

    #endregion

    #region Structs & Enums

    public enum Items
    {
        Ward,
        Watch,
        Counters,
        Log,
        Timers,
        Memory,
        LoadingThreading,
        World,
        WardObjects,
        NetworkObj,
    }

    private struct LogMsg
    {
        public LogType Type;
        public String Msg;
        public LogMsg(string msg, LogType type) { Msg = msg; Type = type; }
    }

    public enum LogType
    {
        Default,
        GeneralError,
        NetworkError,
        ClientError,
        ServerError,
        LoadError,
        LoadEvent,
        GenerationError,
        GenerationEvent,
        GameError,
        GeneralEvent,
        ObjectEvent,
        PlayerEvent,
        PlayerError,
        InputError,
        OutputError,
        UpdateError,
        DrawError,
        IO,
        IOError,
        NetConnection,
        NetDisconnection,
        NetObjCreation,
        NetObjChange,
        NetPeerEvent,
        PlayerJoined,
        PlayerLeft,
        Cmd,
        CmdReturn,
        AbuseWarning,
    }

    #endregion

    #region Input Methods (Public Event)

    /// <summary>
    /// <para>Bind this to your keyinput.charEntered of choice to be able to use the input functionality of Ward</para>
    /// <para>Example WinInput.cs:</para>
    /// <para>WinInput.CharEntered += (s, c) => { Ward.OnCharEntered(c.Character); };</para>
    /// </summary>
    public static void OnCharEntered(char c)
    {
        char.IsNumber(c);
        //if (DateTime.Now > lastInput + InputReset)
        //{
        //    if (!char.IsControl(c))
        //        InputText = c.ToString();
        //}
        //else
        //{
        if (c == '\b')
        {
            if (InputText.Length > 0)
                InputText = InputText.Substring(0, InputText.Length - 1);
        }
        else if (!char.IsControl(c))
            InputText += c.ToString();
        //}

        lastInput = DateTime.Now;

        if (Visible && keyDown(FunctionKey) && xWatch.editIndex >= 0 && xWatch.editIndex < WatchesSorted.Count)
            WatchesSorted[xWatch.editIndex].EditChar(c);

        //if (!string.IsNullOrEmpty(str))
        //{
        //    int num;
        //    if (DateTime.Now > lastInput + InputReset)
        //    {
        //        if (str[0] == '#' && str.Length > 0)
        //        {

        //        }
        //        else
        //            InputText = str;
        //        if (int.TryParse(str, out num))
        //            InputNumeric = num;
        //    }
        //    else
        //    {
        //        if (str[0] == '#' && str.Length > 0)
        //        {
        //            if (str == "#backslash" && InputText.Length > 0)
        //                InputText = InputText.Substring(0, InputText.Length - 1);
        //        }
        //        else
        //            InputText += str;
        //        if (InputNumeric.ToString().Length > 9)
        //            InputNumeric = 0;
        //        if (int.TryParse(str, out num))
        //            InputNumeric = Convert.ToInt64(InputNumeric.ToString() + num.ToString());
        //    }

        //    lastInput = DateTime.Now;
        //}
    }

    private static MouseState ms, msPrev;
    private static KeyboardState ks, ksPrev;
    private static bool isdecimal = false;
    private static bool keyPressed(Keys key)
    {
        return ks.IsKeyDown(key) && ksPrev.IsKeyUp(key);
    }
    private static bool keyDown(Keys key)
    {
        return ks.IsKeyDown(key);
    }

    #endregion

    #region Log (Public)

    /// <summary>
    /// Writes a log message to the log with default type
    /// </summary>
    public static void Log(string Msg, bool ignoreDuplicates = false)
    {
        Log(Msg, LogType.Default, ignoreDuplicates);
    }
    /// <summary>
    /// Writes a log message to the log with the specified LogType
    /// </summary>
    public static void Log(string Msg, LogType Type, bool ignoreDuplicates = false)
    {
        if (ignoreDuplicates && logDupes.ContainsKey(Msg)) return; else logDupes[Msg] = true;

        if (string.IsNullOrEmpty(Msg)) throw new ArgumentNullException("Msg");
        log.Add(new LogMsg(Msg, Type));
        if (OnLog != null)
            OnLog(Msg, Type);
    }

    /// <summary>
    /// Gives a color based on LogType. Types of the same kind are the same color.
    /// </summary>
    public static Color LogTypeColor(LogType type, byte defaultR = 255, byte defaultG = 255, byte defaultB = 255)
    {
        switch (type)
        {
            case LogType.Cmd:
                return Color.Yellow;

            case LogType.CmdReturn:
                return Color.Orange;

            case LogType.Default:
            case LogType.GeneralEvent:
            case LogType.ObjectEvent:
            case LogType.PlayerEvent:
            case LogType.NetPeerEvent:
            case LogType.LoadEvent:
                break;

            case LogType.GeneralError:
            case LogType.NetworkError:
            case LogType.ClientError:
            case LogType.ServerError:
            case LogType.LoadError:
            case LogType.GenerationError:
            case LogType.GameError:
            case LogType.PlayerError:
            case LogType.InputError:
            case LogType.OutputError:
            case LogType.UpdateError:
            case LogType.DrawError:
            case LogType.AbuseWarning:
                return Color.Pink;

            case LogType.IO:
            case LogType.IOError:
                return Color.YellowGreen;

            case LogType.PlayerJoined:
            case LogType.NetConnection:
                return Color.LightGreen;

            case LogType.PlayerLeft:
            case LogType.NetDisconnection:
                return Color.Orange;

            case LogType.NetObjCreation:
                return Color.SkyBlue;

            case LogType.NetObjChange:
                return Color.LightBlue;
        }
        return new Color(defaultR, defaultG, defaultB);
    }

    #endregion

    #region Ward Drawing

    private static void drawWard(SpriteBatch sb
                                    #if XEN
                                 , DrawState state
                                    #endif
                                )
    {
        fpsCounterD++; //FPS Counter

        wtDraw.Start();
#if DEBUG
        if (active && ItemVisible[0])
        {
            sb.Begin();
            leftBot = ItemSpacing;
            rightBot = ItemSpacing;

            for (int i = 0; i < ItemCount; i++)
                DrawItem(sb, 
                            #if XEN
                        state,
                            #endif
                        (Items)i, false);
            sb.End();

            if (ItemVisible[(int)Items.WardObjects])
            {
                sb.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.AnisotropicClamp, DepthStencilState.Default, RasterizerState.CullCounterClockwise, null, view2D);
                DrawItem(sb,
                        #if XEN
                            state,
                        #endif
                         Items.WardObjects, true);
                sb.End();
            }
        }

#endif
        wtDraw.Stop();
    }

    private static int leftBot = 0, rightBot = 0;
    private static bool ColumnLeft;
    //private static void drawStr(string str, Items item, int indent)
    //{

    //    Point size;
    //    if (ColumnLeft)
    //    {
    //        size = DrawStringWithBar(spritebatch, font, str, new Vector2(ItemSpacing + indent, LineSpacing + leftBot), Color.White);
    //        leftBot += LineSpacing + size.Y;
    //    }
    //    else
    //    {
    //        size = DrawStringWithBar(spritebatch, font, str, new Vector2(graphics.Viewport.Width -ItemSpacing - indent, LineSpacing + rightBot), Color.White, true);
    //        rightBot += LineSpacing + size.Y;
    //    }
    //}
    private static void itemDrawn(Point size)
    {
        if (ColumnLeft)
            leftBot += LineSpacing + size.Y;
        else
            rightBot += LineSpacing + size.Y;
    }
    private static void itemEnd()
    {
        if (ColumnLeft)
            leftBot += ItemSpacing;
        else
            rightBot += ItemSpacing;
    }
    private static Vector2 itemPos(float indent, Vector2 size)
    {
        Vector2 output;
        if (ColumnLeft)
        {
            output = new Vector2(indent, -leftBot);
            leftBot += LineSpacing + (int)size.Y;
            return output;
        }
        output = new Vector2(graphics.Viewport.Width - indent - size.X, -rightBot);
        rightBot += LineSpacing + (int)size.Y;
        return output;
    }

    private static void DrawItem(SpriteBatch sb,
                                #if XEN
                                        DrawState state,
                                #endif
                                Items Item,
                                bool worldSpace)
    {
        if (!ItemVisible[(int)Item]) 
            return;

        ColumnLeft = true;

        string drawText = "";
        Vector2 drawSize = itemStringBarDefaultSize;
        Vector2 drawPos = Vector2.Zero;
            
        Sb.Clear();
        switch (Item)
        {
            case Items.Ward:
                Sb.Clear();
                Sb.Append(ItemStr[(int)Item]);
                Sb.Append(string.Concat(" || Upd/Draw ", fpsLast, "/", fpsLastD));
                Sb.Append(" || Input: ");
                if (InputText.Length <= 30)
                    Sb.Append(InputText);
                else
                {
                    Sb.Append("...");
                    Sb.Append(InputText.Substring(InputText.Length - 30, 30));
                }

                drawText = Sb.ToString();

                break;


            case Items.Watch:
                lastWatchLeftBot = leftBot; // used by mouse editing

                Sb.AppendLine(ItemStr[(int)Item]);
                Sb.AppendLine();
                if (WatchesSorted.Count > 0)
                {
                    for (int i = 0; i < WatchesSorted.Count; i++)
                    {
                        var s = WatchesSorted[i];
                        Sb.Append(Indent);
                        if (i + 1 < WatchesSorted.Count)
                            Sb.AppendLine(s.ToString());
                        else
                            Sb.Append(s.ToString());
                        if (s.DrawsSlider)
                        {
                            // Draw Slider for this watch
                            int f = 0;
                            if (i == selWatch)
                                f = 100;
                            if (sb != null)
                                sb.Draw(Pixel, new Rectangle(ItemSpacing * 2, (int)(leftBot + fontHeight * 2 + i * fontHeight + 1), (int)(sliderWidth * s.SliderValue) + f, (int)fontHeight - 2), CoolColor(i));
                            else
                            {
                                //TODO: Xen sliders
                            }
                        }
                    }
                }

                if (LastItemCounts[(int)Items.Watch] != Watches.Count)
                {
                    drawSize = new Vector2(drawSize.X, 10000);
                    drawText = Sb.ToString();

                    LastItemCounts[(int)Items.Watch] = Watches.Count;
                }
                else
                    drawText = Sb.ToString();
                break;


            case Items.Counters:
                Sb.AppendLine(ItemStr[(int)Item]);
                Sb.AppendLine();
                if (CountersSorted.Count > 0)
                {
                    for (int i = 0; i < CountersSorted.Count - 1; i++)
                    {
                        Sb.Append(Indent);
                        Sb.AppendLine(CountersSorted[i]);
                    }
                    Sb.Append(Indent);
                    Sb.Append(CountersSorted[CountersSorted.Count - 1]);
                }

                if (LastItemCounts[(int)Items.Counters] != Counters.Count)
                {
                    drawSize = new Vector2(drawSize.X, 10000);
                    drawText = Sb.ToString();

                    LastItemCounts[(int)Items.Counters] = Counters.Count;
                }
                else
                    drawText = Sb.ToString();

                foreach (var item in Counters)
                    item.Value.Two = 0;
                break;


            case Items.Log:
                ColumnLeft = false;
                //bar.TextHorizontalAlignment = ColumnLeft ? TextHorizontalAlignment.Left : TextHorizontalAlignment.Right;
                Sb.AppendLine(ItemStr[(int)Item]);
                Sb.AppendLine();
                for (int i = log.Count - 1; i >= Math.Max(0, log.Count - 20); i--)
                {
                    Sb.Append(Indent);
                    Sb.AppendLine(log[i].Msg);
                }
                Sb.Append(Indent);
                drawText = Sb.ToString();

                if (LastItemCounts[(int)Items.Log] != log.Count)
                    LastItemCounts[(int)Items.Log] = log.Count;
                break;


            case Items.Timers:
                Sb.Append(ItemStr[(int)Item]);
                drawText = Sb.ToString();
                drawPos = itemPos(ItemSpacing, drawSize);
#if XEN
                if (state != null)
                {
                    TextElementRect bar = ItemStringbars[(int)Item];

                    bar.Position = itemPos(ItemSpacing, bar.Size);
                    bar.TextHorizontalAlignment = ColumnLeft ? TextHorizontalAlignment.Left : TextHorizontalAlignment.Right;
                    bar.Text.SetText(drawText);
                    bar.ValidateBuild();
                    bar.Size = new Vector2(drawSize.X, bar.ContentHeight);
                    bar.Draw(state);
                }
                //TODO: Xen Graph Drawing
#endif
                if (sb != null)
                {
                    sb.DrawString(font, drawText, drawPos, Color.White);

                    foreach (var item in Timers)
                    {
                        item.Value.Position = new Vector2(ItemSpacing + LineSpacing, LineSpacing + leftBot);
                        Point size =
                        item.Value.Draw(sb, font);
                        leftBot += LineSpacing + size.Y;
                    }
                }

                itemEnd();
                return;


            case Items.Memory:
                break;


            case Items.LoadingThreading:
                break;


            case Items.World:
                break;


            case Items.WardObjects:
                #if XEN

                #else
                // 2D
                if (worldSpace)
                {
                    foreach (var item in Lines)
                        item.Value.Draw(sb, font, Pixel, LineThickness);
                    foreach (var item in Boxes)
                        item.Value.Draw(sb, font, Pixel);
                    foreach (var item in Points)
                        item.Value.Draw(sb, font, Pixel, PointSize);
                }

                
                foreach (var item in Lines3D)
                    item.Value.Draw(sb, font, view3D, proj3D, graphics, Pixel, LineThickness);
                foreach (var item in Points3D)
                    item.Value.Draw(sb, font, view3D, proj3D, graphics, Pixel, PointSize);
                #endif
                return;


            case Items.NetworkObj:
                if (NetObjList == null || NetObjList.Count == 0) return;
                
                ColumnLeft = false;
                //bar.TextHorizontalAlignment = ColumnLeft ? TextHorizontalAlignment.Left : TextHorizontalAlignment.Right;

                int index = NetObjScroll - NetObjList.Count + NetObjDrawLen-1;


                Sb.Append(ItemStr[(int)Item]);
                Sb.Append(" (");
                Sb.Append(Math.Max(0, NetObjList.Count - NetObjScroll - NetObjDrawLen)+1);
                Sb.Append(" to ");
                Sb.Append(NetObjList.Count - NetObjScroll+1);
                Sb.Append(" of ");
                Sb.Append(NetObjList.Count);
                Sb.AppendLine(")");
                Sb.AppendLine();

                
                foreach (var item in NetObjList)
                {
                    if (index++ < 0) continue;
                    if (index >= NetObjDrawLen) break;
                    Sb.AppendLine(item.ToString());
                }
                drawText = Sb.ToString();
                if (netUptNext)
                {
                    netUptNext = false;
                    //bar.UpdateBarSize(font);
                }
                break;
        };
        //if (sb != null)
        //{
            // If not using Xen, we need to calculate the height of the string ourselves
            //drawSize.Y = font.MeasureString(drawText).Y; // Expensive but correct way

            // Cheaper way
            int lines = 1;
            for (int i = 0; i < drawText.Length; i++)
                if (drawText[i] == '\n')
                    lines++;
            drawSize.Y = fontHeight * lines;
        //}

#if XEN
        TextElementRect b = ItemStringbars[(int)Item];
        if (state != null)
        {
            b.TextHorizontalAlignment = ColumnLeft ? TextHorizontalAlignment.Left : TextHorizontalAlignment.Right;
            //b.Size = drawSize;
            b.Text.SetText(drawText);
            b.ValidateBuild();
            b.Size = new Vector2(drawSize.X, drawSize.Y);
        }
#endif
        drawPos = itemPos(ItemSpacing, drawSize);

#if XEN
        if (state != null)
        {
            b.Position = drawPos;
            //b.Colour = Color.Black; //TODO: Save seperate text element for shadow, so we dont rebuild it so often
            //b.ReadHexColors = false;
            b.Draw(state);
            //b.Position += new Vector2(-1, 1);
            //b.ReadHexColors = true;
            //b.Colour = Color.White;
            //b.Draw(state);
        }
#endif

        if (sb != null)
        {
            drawPos.Y *= -1; // translate from Xen positioning
            sb.DrawString(font, drawText, new Vector2(drawPos.X +1, drawPos.Y +1), Color.Black);
            sb.DrawString(font, drawText, drawPos, Color.White);
        }

        itemEnd();
    }

    #endregion

    #region Watch & Count (Public)

    //TODO: Watch automatic delegates by sending object+property name
    //TODO: After above, second watch list of automatically created for all properties from "selected object"
    //TODO: Split into two overloads to make easier to understand. one with delegates and one without
    //TODO: Auto min/max values instead. closes 1/10/100 etc value

    #region Show/Hide from watch/count

    private static void checkWatchVisible(xWatch watch)
    {
        watch.Visible = true;
        string name = watch.Name.ToLower();

        // true if found in shows
        foreach (var item in watchShows)
            if (name.Contains(item)) return;

        // hide if in a hide match
        foreach (var item in watchHides)
            if (name.Contains(item))
            {
                watch.Visible = false;
                return;
            }
    }

    /// <summary>
    /// Hides any watch/count which has the given string in it's name.
    /// </summary>
    public static void HideWatch(string match)
    {
        match = match.ToLower();
        if (watchHides.Contains(match) || string.IsNullOrEmpty(match)) return;

        if (watchShows.Contains(match))
        {
            watchShows.Remove(match);

            #region Save to Registry shows

            Sb.Clear();
            for (int i = 0; i < watchShows.Count; i++)
            {
                Sb.Append(watchShows[i]);
                if (i + 1 < watchHides.Count)
                    Sb.Append(" ");
            }
            regKey.SetValue("watchShows", Sb.ToString());

            #endregion
            return;
        }

        watchHides.Add(match);

        #region Save to Registry hides

        Sb.Clear();
        for (int i = 0; i < watchHides.Count; i++)
        {
            Sb.Append(watchHides[i]);
            if (i+1 < watchHides.Count)
                Sb.Append(" ");
        }
        regKey.SetValue("watchHides", Sb.ToString());

        #endregion

        foreach (var item in Watches)
            checkWatchVisible(item.Value);
    }

    /// <summary>
    /// Shows any watch which has the given string in it's name. If the exact string matches a previously hidden string, it will be removed, else an exception rule will be added for the shown string.
    /// </summary>
    public static void ShowWatch(string match)
    {
        if (string.IsNullOrEmpty(match)) return;
        match = match.ToLower();

        //remove if match has been set as hide before
        if (watchHides.Contains(match))
        {
            watchHides.Remove(match);

            #region Save to Registry hides

            Sb.Clear();
            for (int i = 0; i < watchHides.Count; i++)
            {
                Sb.Append(watchHides[i]);
                if (i + 1 < watchHides.Count)
                    Sb.Append(" ");
            }
            regKey.SetValue("watchHides", Sb.ToString());

            #endregion
            return;
        }

        if (watchShows.Contains(match)) return;
        watchShows.Add(match);

        #region Save to Registry shows

        Sb.Clear();
        for (int i = 0; i < watchShows.Count; i++)
        {
            Sb.Append(watchShows[i]);
            if (i + 1 < watchHides.Count)
                Sb.Append(" ");
        }
        regKey.SetValue("watchShows", Sb.ToString());

        #endregion

        foreach (var item in Watches)
            checkWatchVisible(item.Value);
    }

    #endregion

    #region Watch Wrapper Classes

    public abstract class xWatch
    {
        public static int editIndex = -1; // This is the currently selected SortedIndex of an editable watch

        public string Name { get; private set; }
        public int SortedIndex;
        public virtual bool DrawsSlider { get { return false; } }
        public virtual float SliderValue { get { return 0; } }
        public abstract Type Type { get; }
        public bool LoadedFromRegistry { get; protected set; }
        public string FriendlyName { get { return Name.Substring(Name.IndexOf('.') + 1); } }
        public bool Visible = true;
        public bool Editable = true;

        protected bool wantsToloadFromReg;

        public xWatch(string Name, bool loadRegistry)
        {
            wantsToloadFromReg = loadRegistry;
            this.Name = Name;
            checkWatchVisible(this);
        }

        public override string ToString() 
        { 
#if XEN
            if (editIndex == SortedIndex)
                return string.Concat("#F99", Name, " : ", valueString(), "#&");
            return string.Concat(Name, " : ", valueString());
#else
            if (editIndex == SortedIndex)
                return string.Concat(Name.ToUpper(), " : ", valueString());
            return string.Concat(Name, " : ", valueString());
#endif
        }
        protected abstract string valueString();

        public virtual void EditChar(char c)
        {

        }
        public abstract void UpdateValue();
        public abstract void TryLoadReg();
        protected abstract void loadFromReg(object obj);

        public abstract void EditAdd();
        public abstract void EditSubtract();
    }

    public abstract class xWatchT<T> : xWatch where T : IEquatable<T>
    {
        public delegate void TEvent(T value);
        public delegate T TReturnEvent();

        private TEvent Set;
        private TReturnEvent Get;
        public T Value
        {
            get 
            { 
                if (Get != null) 
                    return Get(); 
                return val; 
            }
            set 
            {
                val = value; 
                if (Set != null) 
                    Set(value);
                if (base.wantsToloadFromReg && base.LoadedFromRegistry)
                    saveToReg(Ward.regKeyWatches);
            }
        }
        protected T val;
        public T Default { get; private set; }
        public override Type Type { get { return typeof(T); } }

        protected xWatchT(string name, T Value, bool saveLoadToRegistry = false)
            : base(name, saveLoadToRegistry)
        {
            this.Value = Value;
            this.Default = Value;
            TryLoadReg();
        }

        public xWatchT(string name, TReturnEvent Get, TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, saveLoadToRegistry)
        {
            //if (Set == null && saveLoadToRegistry)
            //    throw new Exception("If you want to use save/load from registry, you must specify a Set delegate.");

            UpdateValue();
            this.Default = Value;
            SetEvents(Get, Set);

            TryLoadReg();
        }

        public void SetEvents(TReturnEvent Get, TEvent Set)
        {
            this.Get = Get;
            this.Set = Set;
        }

        public override void TryLoadReg()
        {
            if (base.wantsToloadFromReg && !base.LoadedFromRegistry && Ward.regKeyWatches != null)
            {
                //if (Ward.regKeyWatches == null)
                //    throw new Exception("If you want to use saving and loading to registry, you must first initialize ward. This should be done automatically when creating a Ward Component, so create that before calling these watches.");

                loadFromReg(Ward.regKeyWatches.GetValue(Name));
                LoadedFromRegistry = true;
            }
        }

        protected override string valueString()
        {
            if (Value == null)
                return "NULL";
 	        return Value.ToString();
        }
        public override void UpdateValue()
        {
            if (Get != null)
            {
                T newVal = Get();
                if (!Object.Equals(Value, newVal))
                {
                    Value = newVal;
                    if (LoadedFromRegistry)
                        saveToReg(Ward.regKeyWatches);
                }
            }
        }
        protected virtual void saveToReg(RegistryKey key)
        {
            if (!Object.Equals(Value, null))
                key.SetValue(Name, Value.ToString());
        }

        public override void EditAdd()
        {
            T val = editAdd();
            Value = val;
        }
        public override void EditSubtract()
        {
            T val = editSubtract();
            Value = val;
        }
        protected abstract T editAdd();
        protected abstract T editSubtract();
    }

    public class xWatchString : xWatchT<string>
    {
        internal xWatchString(string name, string Value, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
        }

        internal xWatchString(string name, TReturnEvent Get, TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
        }

        public override void EditChar(char c)
        {
            string val = Value;
            if (c == '\b')
            {
                if (val.Length > 0)
                    val = val.Substring(0, val.Length - 1);
            }
            else if (!char.IsControl(c))
                val += c.ToString();
            Value = val;
        }

        protected override string editAdd()
        {
            return InputText;
        }
        protected override string editSubtract()
        {
            return "";
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            Value = obj.ToString();
        }

        public static implicit operator string(xWatchString Var)
        {
            return Var.Value;
        }
    }

    public class xWatchFloat : xWatchT<float>
    {
        public static float IncreasePerFrame = 0.002f;

        public float min, max;
        public string valueFormat;

        public override bool DrawsSlider { get { return min != max; } }
        public override float SliderValue { get { return Value / (max - min); } }

        public xWatchFloat(string name, float Value, float min = 0, float max = 0, string valueFormat = "0.000", bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
            this.min = min;
            this.max = max;
            this.valueFormat = valueFormat;
        }

        public xWatchFloat(string name, xWatchFloat.TReturnEvent Get, float min = 0, float max = 0, string valueFormat = "0.000", xWatchFloat.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
            this.min = min;
            this.max = max;
            this.valueFormat = valueFormat;
        }

        protected override string valueString()
        {
 	         return Value.ToString(valueFormat);
        }

        protected override float editAdd()
        {
            return Value + (max - min) * IncreasePerFrame;
        }

        protected override float editSubtract()
        {
            return Value - (max - min) * IncreasePerFrame;
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            Value = Convert.ToSingle(obj, CultureInfo.InvariantCulture);
        }
        protected override void saveToReg(RegistryKey key)
        {
            key.SetValue(Name, Value.ToString(CultureInfo.InvariantCulture));
        }

        public static implicit operator float(xWatchFloat Var)
        {
            return Var.Value;
        }
    }

    public class xWatchInt : xWatchT<int>
    {
        public static float IncreasePerFrame = 0.003f;

        public int min, max;

        public override bool DrawsSlider { get { return min != max; } }
        public override float SliderValue { get { return (float)Value / (max - min); } }

        public xWatchInt(string name, int Value, int min = 0, int max = 0, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
            this.min = min;
            this.max = max;
        }

        public xWatchInt(string name, xWatchInt.TReturnEvent Get, int min = 0, int max = 0, xWatchInt.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
            this.min = min;
            this.max = max;
        }

        protected override int editAdd()
        {
            return Value + Math.Max(1, (int)((max - min) * IncreasePerFrame));
        }

        protected override int editSubtract()
        {
            return Value - Math.Max(1, (int)((max - min) * IncreasePerFrame));
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            Value = Convert.ToInt32(obj);
        }

        public static implicit operator int(xWatchInt Var)
        {
            return Var.Value;
        }
    }

    public class xWatchByte : xWatchT<byte>
    {
        public override bool DrawsSlider { get { return true; } }
        public override float SliderValue { get { return Value / 255f; } }

        public xWatchByte(string name, byte Value, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
        }

        public xWatchByte(string name, xWatchByte.TReturnEvent Get, xWatchByte.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
        }

        protected override byte editAdd()
        {
            return (byte)(Value + 1);
        }

        protected override byte editSubtract()
        {
            return (byte)(Value - 1);
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            int i = Convert.ToInt32(obj);
            Value = (byte)(MathHelper.Clamp(i, 0, 255));
        }
    }

    public class xWatchBool : xWatchT<bool>
    {
        public xWatchBool(string name, bool Value, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
        }

        public xWatchBool(string name, xWatchBool.TReturnEvent Get, xWatchBool.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
        }

        protected override bool editAdd()
        {
            return true;
        }

        protected override bool editSubtract()
        {
            return false;
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            Value = Convert.ToInt16(obj) != 0;
        }
        protected override void saveToReg(RegistryKey key)
        {
            key.SetValue(Name, Value ? 1 : 0);
        }
        public static implicit operator bool(xWatchBool Var)
        {
            return Var.Value;
        }
    }

    public class xWatchPoint : xWatchT<Point>
    {
        public xWatchPoint(string name, Point Value, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        { }

        public xWatchPoint(string name, xWatchPoint.TReturnEvent Get, xWatchPoint.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
        }

        protected override Point editAdd()
        {
            Point p = Value;
            p.X++;
            p.Y++;
            return p;
        }

        protected override Point editSubtract()
        {
            Point p = Value;
            p.X--;
            p.Y--;
            return p;
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            string[] split = (obj.ToString()).Split(' ');
            if (split.Length != 2) return;
            Value = new Point(Convert.ToInt32(split[0], CultureInfo.InvariantCulture), Convert.ToInt32(split[1], CultureInfo.InvariantCulture));
        }

        protected override void saveToReg(RegistryKey key)
        {
            if (!Object.Equals(Value, null))
                key.SetValue(Name, string.Concat(Value.X, " ", Value.Y));
        }

        public static implicit operator Point(xWatchPoint Var)
        {
            return Var.Value;
        }
    }

#if XNA
    public class xWatchVector2 : xWatchT<Vector2>
    {
        public static float IncreasePerFrame = 0.003f;

        public string valueFormat;

        public xWatchVector2(string name, Vector2 Value, string valueFormat = "0.00", bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
            this.valueFormat = valueFormat;
        }

        public xWatchVector2(string name, xWatchVector2.TReturnEvent Get, string valueFormat = "0.00", xWatchVector2.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
            this.valueFormat = valueFormat;
        }

        protected override string valueString()
        {
            return string.Concat(Value.X.ToString(valueFormat), ", ", Value.Y.ToString(valueFormat));
        }

        protected override Vector2 editAdd()
        {
            return Value + new Vector2(IncreasePerFrame);
        }

        protected override Vector2 editSubtract()
        {
            return Value - new Vector2(IncreasePerFrame);
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            string[] split = (obj.ToString()).Split(' ');
            if (split.Length != 2) return;
            Value = new Vector2(Convert.ToSingle(split[0], CultureInfo.InvariantCulture), Convert.ToSingle(split[1], CultureInfo.InvariantCulture));
        }
        protected override void saveToReg(RegistryKey key)
        {
            key.SetValue(Name, string.Concat(Value.X.ToString(CultureInfo.InvariantCulture), " ", Value.Y.ToString(CultureInfo.InvariantCulture)));
        }

        public static implicit operator Vector2(xWatchVector2 Var)
        {
            return Var.Value;
        }
    }

    public class xWatchVector3 : xWatchT<Vector3>
    {
        public static float IncreasePerFrame = 0.003f;

        public string valueFormat;

        public xWatchVector3(string name, Vector3 Value, string valueFormat = "0.00", bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        {
            this.valueFormat = valueFormat;
        }

        public xWatchVector3(string name, xWatchVector3.TReturnEvent Get, string valueFormat = "0.00", xWatchVector3.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
            this.valueFormat = valueFormat;
        }

        protected override string valueString()
        {
            return string.Concat(Value.X.ToString(valueFormat), ", ", Value.Y.ToString(valueFormat), ", ", Value.Z.ToString(valueFormat));
        }

        protected override Vector3 editAdd()
        {
            return Value + new Vector3(IncreasePerFrame);
        }

        protected override Vector3 editSubtract()
        {
            return Value - new Vector3(IncreasePerFrame);
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            string[] split = (obj.ToString()).Split(' ');
            if (split.Length != 3) return;
            Value = new Vector3(Convert.ToSingle(split[0], CultureInfo.InvariantCulture), Convert.ToSingle(split[1], CultureInfo.InvariantCulture), Convert.ToSingle(split[2], CultureInfo.InvariantCulture));
        }
        protected override void saveToReg(RegistryKey key)
        {
            key.SetValue(Name, string.Concat(Value.X.ToString(CultureInfo.InvariantCulture), " ", Value.Y.ToString(CultureInfo.InvariantCulture), " ", Value.Z.ToString(CultureInfo.InvariantCulture)));
        }

        public static implicit operator Vector3(xWatchVector3 Var)
        {
            return Var.Value;
        }
    }
#endif

#if TOOLS

    public class xWatchPoint2 : xWatchT<Point2>
    {
        public xWatchPoint2(string name, Point2 Value, bool saveLoadToRegistry = false)
            : base(name, Value, saveLoadToRegistry)
        { }

        public xWatchPoint2(string name, xWatchPoint2.TReturnEvent Get, xWatchPoint2.TEvent Set = null, bool saveLoadToRegistry = false)
            : base(name, Get, Set, saveLoadToRegistry)
        {
        }

        protected override Point2 editAdd()
        {
            Point2 p = Value;
            p.X++;
            p.Y++;
            return p;
        }

        protected override Point2 editSubtract()
        {
            Point2 p = Value;
            p.X--;
            p.Y--;
            return p;
        }

        protected override void loadFromReg(object obj)
        {
            if (obj == null) return;
            string[] split = (obj.ToString()).Split(' ');
            if (split.Length != 2) return;
            Value = new Point2(Convert.ToInt32(split[0], CultureInfo.InvariantCulture), Convert.ToInt32(split[1], CultureInfo.InvariantCulture));
        }

        protected override void saveToReg(RegistryKey key)
        {
            if (!Object.Equals(Value, null))
                key.SetValue(Name, string.Concat(Value.X, " ", Value.Y));
        }

        public static implicit operator Point2(xWatchPoint2 Var)
        {
            return Var.Value;
        }
    }

#endif

    #endregion

    /// <summary>
    /// returns the watch associated with the name, or creates a new one if it does not exist or match the type. will always return a xWatch of type T
    /// </summary>
    private static xWatch tryWatch<T>(string name, T defaultValue, xWatchT<T>.TReturnEvent Get = null, xWatchT<T>.TEvent Set = null) where T : IEquatable<T>
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        xWatch w;
        if (Watches.TryGetValue(name, out w))
            if (typeof(T) == w.Type)
                w = null;

        if (w != null)
        {
            (w as xWatchT<T>).SetEvents(Get, Set);
            if (Get == null)
                (w as xWatchT<T>).Value = defaultValue;
        }

        return w;

        //if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        //watch w;
        //if (Watches.TryGetValue(name, out w))
        //    if (w is watchFloat == false)
        //        w = null;

        //if (w == null)
        //    w = new watchFloat(name, Get, min, max, valueFormat, Set);
        //else
        //{
        //    (w as watchFloat).Get = Get;
        //    (w as watchFloat).min = min;
        //    (w as watchFloat).max = max;
        //    (w as watchFloat).valueFormat = valueFormat;
        //    (w as watchFloat).Set = Set;
        //}

        //Watches[name] = w;
    }

    #region WatchFloat

    public static xWatchFloat WatchDynamic(string name, xWatchFloat.TReturnEvent Get, xWatchFloat.TEvent Set = null, float min = 0, float max = 0, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<float>(name, 0, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchFloat(name, Get, min, max, valueFormat, Set, saveLoadToRegistry);
        else
        {
            (w as xWatchFloat).min = min;
            (w as xWatchFloat).max = max;
            (w as xWatchFloat).valueFormat = valueFormat;
        }
        return w as xWatchFloat;
    }

    public static xWatchFloat Watch(string name, float value, float min = 0, float max = 0, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<float>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchFloat(name, value, min, max, valueFormat, saveLoadToRegistry);
        else
        {
            (w as xWatchFloat).min = min;
            (w as xWatchFloat).max = max;
            (w as xWatchFloat).valueFormat = valueFormat;
        }
        return w as xWatchFloat;
    }

    #endregion

    #region WatchString

    public static xWatchString WatchDynamic(string name, xWatchString.TReturnEvent Get, xWatchString.TEvent Set = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<string>(name, null, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchString(name, Get, Set, saveLoadToRegistry);
        return w as xWatchString;
    }

    public static xWatchString Watch(string name, string value, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<string>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchString(name, value, saveLoadToRegistry);
        return w as xWatchString;
    }

    #endregion

    #region WatchByte

    public static xWatchByte WatchDynamic(string name, xWatchByte.TReturnEvent Get, xWatchByte.TEvent Set = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<byte>(name, 0, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchByte(name, Get, Set, saveLoadToRegistry);
        return w as xWatchByte;
    }

    public static xWatchByte Watch(string name, byte value, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<byte>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchByte(name, value, saveLoadToRegistry);
        return w as xWatchByte;
    }

    #endregion

    #region WatchInt

    public static xWatchInt WatchDynamic(string name, xWatchInt.TReturnEvent Get, xWatchInt.TEvent Set = null, int min = 0, int max = 0, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<int>(name, 0, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchInt(name, Get, min, max, Set, saveLoadToRegistry);
        else
        {
            (w as xWatchInt).min = min;
            (w as xWatchInt).max = max;
        }
        return w as xWatchInt;
    }

    public static xWatchInt Watch(string name, int value, int min = 0, int max = 0, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<int>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchInt(name, value, min, max, saveLoadToRegistry);
        else
        {
            (w as xWatchInt).min = min;
            (w as xWatchInt).max = max;
        }
        return w as xWatchInt;
    }

    #endregion

    #region WatchBool

    public static xWatchBool WatchDynamic(string name, xWatchBool.TReturnEvent Get, xWatchBool.TEvent Set = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<bool>(name, false, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchBool(name, Get, Set, saveLoadToRegistry);
        return w as xWatchBool;
    }

    /// <summary>
    /// <para>Keeps track of a value for you and displays it in the ward interface.</para>
    /// <para>You can either use the direct value overloads (asking for float, string, etc) or specify delegates to GET/SET your values</para>
    /// <para>When setting values directly, call them each time you want to update the value.</para>
    /// <para>When using delegate method, the value will automatically update each frame, so ONLY CALL IT ONCE</para>
    /// <para>When using the delegate method, you may specify a SET delegate, if you do, you can edit the value by holding the F12 key and using arrows and sometimes keys depending on the value</para>
    /// <para>Some numeric values may also show a slider if you specify the range.</para>
    /// <para>When having both a SET and GET delegates, you may also choose to save/load the value from the registry automatically. Make sure these are created AFTER you create your ward component.</para>
    /// <para>Keep in mind that you may only save values where the SET delegate sets the value correctly when the watch is first called.</para>
    /// </summary>
    public static xWatchBool Watch(string name, bool value, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<bool>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchBool(name, value, saveLoadToRegistry);
        return w as xWatchBool;
    }

    #endregion

    #region WatchVector2 & WatchVector3
#if XNA

    public static xWatchVector2 WatchDynamic(string name, xWatchVector2.TReturnEvent Get, xWatchVector2.TEvent Set = null, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Vector2>(name, Vector2.Zero, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchVector2(name, Get, valueFormat, Set, saveLoadToRegistry);
        else
            (w as xWatchFloat).valueFormat = valueFormat;
        return w as xWatchVector2;
    }
    public static xWatchVector2 Watch(string name, Vector2 value, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Vector2>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchVector2(name, value, valueFormat, saveLoadToRegistry);
        else
            (w as xWatchVector2).valueFormat = valueFormat;
        return w as xWatchVector2;
    }

    public static xWatchVector3 WatchDynamic(string name, xWatchVector3.TReturnEvent Get, xWatchVector3.TEvent Set = null, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Vector3>(name, Vector3.Zero, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchVector3(name, Get, valueFormat, Set, saveLoadToRegistry);
        else
            (w as xWatchFloat).valueFormat = valueFormat;
        return w as xWatchVector3;
    }
    public static xWatchVector3 Watch(string name, Vector3 value, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Vector3>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchVector3(name, value, valueFormat, saveLoadToRegistry);
        else
            (w as xWatchVector3).valueFormat = valueFormat;
        return w as xWatchVector3;
    }

#endif
    #endregion

    #region WatchPoint & WatchPoint2

    public static xWatchPoint WatchDynamic(string name, xWatchPoint.TReturnEvent Get, xWatchPoint.TEvent Set = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Point>(name, Point.Zero, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchPoint(name, Get, Set, saveLoadToRegistry);
        return w as xWatchPoint;
    }
    public static xWatchPoint Watch(string name, Point value, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Point>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchPoint(name, value, saveLoadToRegistry);
        return w as xWatchPoint;
    }

    public static xWatchPoint2 WatchDynamic(string name, xWatchPoint2.TReturnEvent Get, xWatchPoint2.TEvent Set = null, bool saveLoadToRegistry = false)
    {
        xWatch w = tryWatch<Point2>(name, Point2.Zero, Get, Set);
        if (w == null)
            Watches[name] = w = new xWatchPoint2(name, Get, Set, saveLoadToRegistry);
        return w as xWatchPoint2;
    }
    public static xWatchPoint2 Watch(string name, Point2 value, bool saveLoadToRegistry = false, string valueFormat = null)
    {
        xWatch w = tryWatch<Point2>(name, value);
        if (w == null)
            Watches[name] = w = new xWatchPoint2(name, value, saveLoadToRegistry);
        return w as xWatchPoint2;
    }

    #endregion

    #region WatchVariable

    /// <summary>
    /// Automatically creates a dynamic watch from the name of a property or field.
    /// </summary>
    public static xWatch WatchVariable(string name, object obj, string fieldOrPropertyName, float min = 0, float max = 0, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        Type type;
        MemberInfo member = obj.GetType().GetProperty(fieldOrPropertyName);
        if (member == null)
        {
            member = obj.GetType().GetField(fieldOrPropertyName);
            type = (member as FieldInfo).FieldType;
        }
        else
            type = (member as PropertyInfo).PropertyType;

        if (member == null)
            throw new Exception(String.Concat("No property or field found with name '", name, "' in object '", obj, "."));

        return watchFromType(type, member, name, obj, fieldOrPropertyName, min, max, valueFormat, saveLoadToRegistry);
    }

    private static void memberType(MemberInfo member)
    {
        //if (member is PropertyInfo)
        //    (member as PropertyInfo).
    }
    private static void memberSet(MemberInfo member, object obj, object value)
    {
        if (member is PropertyInfo)
        {
            if ((member as PropertyInfo).PropertyType != value.GetType()) 
                return;
            (member as PropertyInfo).SetValue(obj, value, null);
        }
        else
        {
            if ((member as FieldInfo).FieldType != value.GetType()) 
                return;
            (member as FieldInfo).SetValue(obj, value);
        }
    }
    private static object memberGet(MemberInfo member, object obj)
    {
        if (member is PropertyInfo)
            return (member as PropertyInfo).GetValue(obj, null);
        else
            return (member as FieldInfo).GetValue(obj);
    }
    private static xWatch watchFromType(Type type, MemberInfo member, string name, object obj, string fieldOrPropertyName, float min = 0, float max = 0, string valueFormat = null, bool saveLoadToRegistry = false)
    {
        name = string.Concat(name, ".", fieldOrPropertyName);

        if (type == typeof(float)) return WatchDynamic(name, () => (float)memberGet(member, obj), (f) => memberSet(member, obj, f), min, max, valueFormat, saveLoadToRegistry);
        else if (type == typeof(int)) return WatchDynamic(name, () => (int)memberGet(member, obj), (f) => memberSet(member, obj, f), (int)min, (int)max, saveLoadToRegistry);
        else if (type == typeof(byte)) return WatchDynamic(name, () => (byte)memberGet(member, obj), (f) => memberSet(member, obj, f), saveLoadToRegistry);
        else if (type == typeof(bool)) return WatchDynamic(name, () => (bool)memberGet(member, obj), (f) => memberSet(member, obj, f), saveLoadToRegistry);
        else if (type == typeof(Point)) return WatchDynamic(name, () => (Point)memberGet(member, obj), (f) => memberSet(member, obj, f), saveLoadToRegistry);
        else if (type == typeof(Point2)) return WatchDynamic(name, () => (Point2)memberGet(member, obj), (f) => memberSet(member, obj, f), saveLoadToRegistry);
#if XNA
        else if (type == typeof(Vector2)) return WatchDynamic(name, () => (Vector2)memberGet(member, obj), (f) => memberSet(member, obj, f), valueFormat, saveLoadToRegistry);
        else if (type == typeof(Vector3)) return WatchDynamic(name, () => (Vector3)memberGet(member, obj), (f) => memberSet(member, obj, f), valueFormat, saveLoadToRegistry);
#endif
        else return WatchDynamic(name, () => memberGet(member, obj).ToString(), (f) => memberSet(member, obj, f), saveLoadToRegistry); // default to string if non supported
    }

    #endregion

    #region WatchObject

    /// <summary>
    /// Automatically creates dynamic watches for all of a given objects properties and fields.
    /// </summary>
    public static void WatchObject(string name, object obj, bool saveLoadToRegistry = false)
    {
        var props = obj.GetType().GetProperties();
        var fields = obj.GetType().GetFields();

        foreach (var item in props)
            watchFromType(item.PropertyType, item, name, obj, item.Name, saveLoadToRegistry: saveLoadToRegistry);
        foreach (var item in fields)
            watchFromType(item.FieldType, item, name, obj, item.Name, saveLoadToRegistry: saveLoadToRegistry);
    }

    #endregion

    /// <summary>
    /// Removes the watch with the specified name
    /// </summary>
    public static void RemoveWatch(string name)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        Watches.Remove(name);
    }

    /// <summary>
    /// Adds +1 to the specified counter value
    /// </summary>
    /// <param name="name"></param>
    public static void Count(string name)
    {
#if DEBUG
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        //if (!active) return;

        TwoInts found;
        if (Counters.TryGetValue(name.ToLower(), out found))
        {
            found.One++;
            found.Two++;
        }
        else
            Counters[name.ToLower()] = new TwoInts(1, 1);
#endif
    }
    /// <summary>
    /// Adds the value to the specified counter value
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    public static void Count(string name, int value)
    {
#if DEBUG
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
        if (!active) return;

            TwoInts found;
            if (Counters.TryGetValue(name.ToLower(), out found))
            {
                found.One += value;
                found.Two += value;
            }
            else
                Counters.Add(name.ToLower(), new TwoInts(value, value));
#endif
    }

    #endregion

    #region Graphs & Timers (Public)

    public static void SetGraph(string GraphName, float HeightMs, int Width, int Height, bool Addative, Color bgColor)
    {
        if (string.IsNullOrEmpty(GraphName)) throw new ArgumentNullException("GraphName");
        if (HeightMs <= 0) throw new Exception("HeightMs must be above 0");
        if (Width <= 0) throw new Exception("Width must be above 0");
        if (Height <= 0) throw new Exception("Height must be above 0");

        //if (!active) return;
        TimerGraph col;
        if (!Timers.TryGetValue(GraphName, out col))
            Timers.Add(GraphName, new TimerGraph(GraphName, HeightMs, Width, Height, bgColor, Addative, graphics));
        
    }
    public static void SetGraph(string GraphName, float HeightMs, int Width, int Height, bool Addative, byte ColorR, byte ColorG, byte ColorB, byte ColorA)
    {
        SetGraph(GraphName, HeightMs, Width, Height, Addative, new Color(ColorR, ColorG, ColorG, ColorA));
    }
    public static Color CoolColor(int id)
    {
        id %= 21;
        switch (id)
        {
            case 0: return new Color(100, 179, 247);
            case 1: return new Color(107, 233, 73);
            case 2: return new Color(241, 217, 104);
            case 3: return new Color(248, 112, 58);
            case 4: return new Color(248, 58, 143);
            case 5: return new Color(193, 108, 247);
            case 6: return new Color(69, 116, 251);
            case 7: return new Color(126, 215, 199);
            case 8: return new Color(74, 178, 89);
            case 9: return new Color(157, 192, 37);
            case 10: return new Color(169, 25, 15);
            case 11: return new Color(156, 15, 169);
            case 12: return new Color(68, 64, 150);
            case 13: return new Color(72, 138, 81);
            case 14: return new Color(160, 89, 14);
            case 15: return new Color(165, 207, 228);
            case 16: return new Color(235, 165, 223);
            case 17: return new Color(165, 223, 235);
            case 18: return new Color(196, 235, 165);
            case 19: return new Color(235, 205, 165);
            case 20: return new Color(218, 165, 235);
            default: return new Color(190, 228, 165);
        }
    }
    public static Timer CreateTimer(string GraphName, string TimerName, int CoolColorID = -1)
    {
        if (CoolColorID < 0) CoolColorID = rnd.Next(21);
        return CreateTimer(GraphName, TimerName, CoolColor(CoolColorID));
    }
    public static Timer CreateTimer(string GraphName, string TimerName, Color color)
    {
        if (string.IsNullOrEmpty(TimerName)) throw new ArgumentNullException("TimerName");

        //if (!active) return NullTimer;
        if (GraphName == null)
            return new Timer(null, TimerName, color);
        TimerGraph col;
        if (!Timers.TryGetValue(GraphName, out col))
            throw new Exception("You need to create a graph named '" + GraphName + "' first, or specify GraphName as null");
        return col.CreateTimer(TimerName, color);
    }
    public static void SetGraphTotal(string GraphName, Timer timer)
    {
        if (string.IsNullOrEmpty(GraphName)) throw new ArgumentNullException("GraphName");

        TimerGraph col;
        if (!Timers.TryGetValue(GraphName, out col))
            throw new Exception("You need to create a graph named '" + GraphName + "' first");
        col.ActualTotal = timer;
    }

    #endregion

    #region Classes: TimerGraph, Graph, Timer //TODO: Xen Drawing

    class TimerGraph
    {
        Graph graph;
        List<Timer> Timers = new List<Timer>();
        float FullHeight, DrawMultiplier = 1;
        public bool Addative { get; private set; }
        public string Name { get; private set; }
        public Timer ActualTotal;
        TextColorValue ActualSlow;
        public Vector2 Position { get { return graph.Position; } set { graph.Position = value; } }

        public TimerGraph(string Name, float fullHeightMs, int GraphHistoryCount, int graphHeight, Color bgColor, bool addative, GraphicsDevice gd)
        {
            this.Name = Name;
            graph = new Graph(GraphHistoryCount, graphHeight, bgColor, gd);
            FullHeight = fullHeightMs;
            if (addative)
            {
                DrawMultiplier = 2;
                Addative = true;
            }
        }

        public Timer CreateTimer(string TimerName, Color color)
        {
            Timer time;
            foreach (var item in Timers)
                if (item.Name == TimerName)
                    return item;
            Timers.Add(time = new Timer(this, TimerName, color));
            return time;
        }

        float Total = 0, TotalInnertia = 0;
        List<TextColorValue> SlowList = new List<TextColorValue>();
        public void Update()
        {

            //foreach (var item in Timers)
            //{
            //    float f = item.LatestTime / FullHeight;
            //    if (text != null)
            //    {
            //        if (f < 0.01)
            //        {
            //            text.Value3 -= 3;
            //            if (text.Value3 < 0)
            //            {
            //                SlowList.Remove(text);
            //                text = null;
            //            }
            //        }

            //        if (text != null)
            //        {
            //            float diff = Math.Abs((item.LatestTime - text.Value) / item.LatestTime);
            //            if (diff > 0.8f)
            //                text.Value = item.LatestTime;
            //            text.ChangeValue(item.LatestTime, 0.006f);
            //            if (text.Value3 < 200)
            //                text.Value3++;
            //            if (text.Value2 < 200)
            //                text.Value2++;
            //        }
            //    }
            //    else if (f >= 0.02f)
            //        SlowList.Add(text = new TextColorValue(item.Name, item.Color, item.LatestTime, 0, 200));
            //}
            //SortList();

            Total = 0;
            foreach (var item in Timers)
                Total += item.LatestTime;

            if (ActualTotal != null)
            {
                //ActualTotal.LatestTime -= Total;
                if (ActualSlow == null)
                    SlowList.Add(ActualSlow = new TextColorValue("Missing", new Color(100,100,100), ActualTotal.LatestTime, 0, 0));

                ActualSlow.Value -= Total;
                if (ActualTotal.LatestTime > 0)
                    RateSlow(ActualTotal, ActualSlow);
                else
                    ActualSlow.Value = 0;
            }

            foreach (var item in Timers)
            {
                if (item.SlowList == null)
                    SlowList.Add(item.SlowList = new TextColorValue(item.Name, item.Color, item.LatestTime, 0,  item.LatestTime / FullHeight > 0.2f ? 30 : -10));

                RateSlow(item, item.SlowList);
            }
            SortList();

            total = 0;
            foreach (var item in Timers)
                writeTimer(item.LatestTime, item.Color);
            if (ActualTotal != null)
                writeTimer(ActualSlow.Value, ActualTotal.Color);

            float d = Math.Abs((Total - TotalInnertia) / Total);
            if (d > 0.8f)
                TotalInnertia = Total;
            TotalInnertia += (Total - TotalInnertia) * 0.02f;


            if (ItemVisible[(int)Items.LoadingThreading])
                graph.Update();
            //graph.Write(0, Color.White);
            if (DrawMultiplier > 1)
                graph.Write(1 / DrawMultiplier, Color.White);
        }

        private void RateSlow(Timer timer, TextColorValue text)
        {
            if (text.Value < 0.01f && text.Value3 > -8)
                text.Value3 -= 2;
            else if (text.Value3 < 100)
                text.Value3++;

            float diff = Math.Abs((timer.LatestTime - text.Value) / timer.LatestTime);
            if (diff > 0.8f)
                text.Value = timer.LatestTime;
            text.ChangeValue(timer.LatestTime, 0.016f);

            if (text.Value2 < 200)
                text.Value2++;
        }
        private float total;
        private void writeTimer(float value, Color color)
        {
            float time = (float)(value / FullHeight);
            total += time;
            if (Addative)
                graph.Write(total / DrawMultiplier, color);
            else
                graph.Write(time / DrawMultiplier, color);
        }

        private void SortList()
        {
            for (int i = 1; i < SlowList.Count; i++)
            {
                if (SlowList[i].Value2 > 60 && SlowList[i].Value > SlowList[i - 1].Value)
                {
                    TextColorValue temp = SlowList[i];
                    SlowList[i] = SlowList[i - 1];
                    SlowList[i - 1] = temp;
                    SlowList[i].Value2 = 0;
                    SlowList[i - 1].Value2 = 0;
                }
            }
            //SlowList.Sort((a, b) => { return b.Value.CompareTo(a.Value); });
            if (Addative)
                Timers.Sort((a, b) => { return a.LatestTime.CompareTo(b.LatestTime); });
        }

        StringBuilder Sb = new StringBuilder();

        public Point Draw(SpriteBatch sb, SpriteFont font)
        {
            int i = 0;
            Sb.Clear();
            Sb.Append(Name);
            Sb.Append(" ");
            Sb.Append(Math.Round(TotalInnertia / FullHeight, 5).ToString("00.0%"));
            Sb.Append(" (");
            Sb.Append(Math.Round(TotalInnertia, 1).ToString("0.0"));
            Sb.Append("ms)");
            //XEN StringBar.DrawStatic(new Vector2(position.X, position.Y + i * 20), spritebatch, font, Pixel, !ColumnLeft, Sb.ToString(), new Microsoft.Xna.Framework.Point(260,20), Color.White);

            int c = 0;
            foreach (var item in SlowList)
            {
                if (c++ > 5) break;
                if (item.Value3 < 0) continue;

                double f = item.Value / FullHeight;
                
                Sb.Clear();
                
                Sb.Append(item.Text);
                Sb.Append(" ");
                Sb.Append(f.ToString("00.0%"));
                Sb.Append("% (");
                Sb.Append(item.Value.ToString("0.0"));
                Sb.Append("ms)");
                i++;
                //XEN StringBar.DrawStatic(new Vector2(position.X + ItemSpacing, position.Y + i * ItemSpacing), spritebatch, font, Pixel, !ColumnLeft, Sb.ToString(), new Microsoft.Xna.Framework.Point(200 - ItemSpacing, 20), item.Color);
            }

            if (ItemVisible[(int)Items.LoadingThreading])
                graph.Draw(sb);
            return new Point(graph.Width, graph.Height);
        }
    }

    class Graph // Copied from Tools.GraphTex2D, but should be here so that Ward is standalone
    {
        public Texture2D Texture { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }
        public Vector2 Position;

        protected Color[] texData;

        private int index;
        private Color BgColor;
        private GraphicsDevice gd;

        public Graph(int width, int height, Color bgColor, GraphicsDevice graphics)
        {
            gd = graphics;
            Texture = new Texture2D(gd, width, height);
            Width = width;
            Height = height;
            BgColor = bgColor;
            texData = new Color[Width * Height];
            for (int i = 0; i < texData.Length; i++)
                texData[i] = bgColor;
        }

        public void Write(float heightPercentige, Color color)
        {
            Write((int)(heightPercentige * Height), color);
        }
        public void Write(int heightPixels, Color color)
        {
            heightPixels = Height - heightPixels;
            heightPixels = (int)MathHelper.Clamp(heightPixels, 0, Height - 1);

            texData[heightPixels * Width + index] = color;
        }

        public void Update()
        {
            Color c = texData[index];
            for (int y = 0; y < Height; y++)
            {
                int i = y * Width + index;
                Color d = texData[i];
                if (d == Color.White)
                    texData[i] = d;
                else if (d != c && d != BgColor)
                {
                    Color n = texData[i];
                    texData[i] = new Color((c.R + n.R) / 2, (c.G + n.G) / 2, (c.G + n.G) / 2);
                    c = n;
                }
                else
                    texData[i] = c;
            }

            gd.Textures[0] = null;
            Texture.SetData<Color>(texData);

            index++;
            if (index >= Width)
                index = 0;
            for (int y = 0; y < Height; y++)
                texData[y * Width + index] = BgColor;
        }

        public void Draw(SpriteBatch sb)
        {
            sb.Draw(Texture, Position, Color.White);
        }

#if XEN
        public void Draw(DrawState state)
        {

        }
#endif
    }

    public class Timer //TODO: See over public methods
    {
        public TextColorValue SlowList;
        public string Name { get; set; }
        public Color Color { get; private set; }
        Ward.TimerGraph Graph;
        public float LatestTime { get; set; }
        //public double[] times = new double[FrameAvarageCount];
        int index = 0;

        public Timer(object graph, string Name, Color color)
        {
            if (graph != null)
            {
                if (graph is Ward.TimerGraph)
                    Graph = graph as Ward.TimerGraph;
                else
                    throw new Exception("Type must be Ward.TimerGraph! ie. Use Ward.CreateGraph(), and then Ward.Timer() instead of creating this class.");
            }
            this.Name = Name;
            Color = color;
        }
        public Timer(object graph, string Name, Color color, float InitialTime) : this(graph, Name, color)
        {
            LatestTime = InitialTime;
        }

        private DateTime start;
        public void Start()
        {
            start = DateTime.Now;
        }
        public void Stop()
        {
            if (Graph == null) return;

            //times[index] = (DateTime.Now - start).TotalMilliseconds;
            //index++;
            //if (index >= times.Length)
            //    index = 0;

            //LatestTime = (float)times.Average();
            LatestTime = (float)(DateTime.Now - start).TotalMilliseconds;
        }

        public void Stop(bool Append)
        {
            if (Graph == null) return;
            if (Append)
                LatestTime += (float)(DateTime.Now - start).TotalMilliseconds;
            else
                LatestTime = (float)(DateTime.Now - start).TotalMilliseconds;
        }
    }

    #endregion

    #region Add/Remove: Lines, Boxes & Points (Public) //TODO: Add drawing, add camera matrix drawing

    /// <summary>
    /// Specify 3D camera matrices for using 3D objects such as lines and points
    /// </summary>
    public static void Set3DMatrices(Matrix projection, Matrix view)
    {
        proj3D = projection;
        view3D = view;
    }

    /// <summary>
    /// Specify 2D camera matrices for using 2D objects such as lines and points
    /// </summary>
    public static void Set2DCamera(Matrix view)
    {
        view2D = view;
    }

    public static void SetLine(string name, Vector2 pos1, Vector2 pos2, Color color)
    {
#if DEBUG
        if (!active) return;

        var line = new LineLabeled(pos1, pos2, color, name);

        if (Lines.ContainsKey(name))
            Lines[name] = line;
        else
            Lines.Add(name, line);
#endif
    }

    public static void SetRect(string name, Vector2 position, Vector2 size, Color color, float rotation)
    {
#if DEBUG
        if (!active) return;

        BoxLabeled box = new BoxLabeled(position, size, color, rotation, name);
        if (Boxes.ContainsKey(name))
            Boxes[name] = box;
        else
            Boxes.Add(name, box);
#endif
    }

    public static void SetPoint(string name, Vector2 position, Color color)
    {
#if DEBUG
        if (!active) return;

        PointLabeled box = new PointLabeled(position, color, 0, name);
        if (Points.ContainsKey(name))
            Points[name] = box;
        else
            Points.Add(name, box);
#endif
    }

    public static void SetPoint3D(string name, Vector3 position, Color color)
    {
#if DEBUG
        if (!active) return;

        Point3DLabeled box = new Point3DLabeled(position, color, name);
        if (Points3D.ContainsKey(name))
            Points3D[name] = box;
        else
            Points3D.Add(name, box);
#endif
    }

    public static void SetLine3D(string name, Vector3 start, Vector3 end, Color color)
    {
#if DEBUG
        if (!active) return;

        Line3DLabeled box = new Line3DLabeled(start, end, color, name);
        if (Lines3D.ContainsKey(name))
            Lines3D[name] = box;
        else
            Lines3D.Add(name, box);
#endif
    }

    public static void RemoveLine(string name)
    {
#if DEBUG
        if (!active) return;
        Lines.Remove(name);
#endif
    }
    public static void RemoveBox(string name)
    {
#if DEBUG
        if (!active) return;
        Boxes.Remove(name);
#endif
    }
    public static void RemovePoint(string name)
    {
#if DEBUG
        if (!active) return;
        Points.Remove(name);
#endif
    }
    public static void RemovePoint3D(string name)
    {
#if DEBUG
        if (!active) return;
        Points3D.Remove(name);
#endif
    }
    public static void RemoveLine3D(string name)
    {
#if DEBUG
        if (!active) return;
        Lines3D.Remove(name);
#endif
    }

    #endregion
}

#region Classes

//public delegate void EventGeneric(object sender, object obj);
//public class StringBar
//{
//    private TextElementRect element;
//    public string Text { get; private set; }
//    public Point Size { get; private set; }
//    public Color Color = Color.White;
//    public bool AlignRight { get; set; }

//    public void SetText(string text, SpriteFont font)
//    {
//        Text = text;
//        if (Size == Point.Zero)
//            UpdateBarSize(font);
//    }

//    private Rectangle drawRec;

//    public void UpdateBarSize(SpriteFont font)
//    {
//        Vector2 measures = font.MeasureString(Text);
//        Size = new Microsoft.Xna.Framework.Point((int)measures.X + 10, (int)measures.Y + 8);
//    }

//    public void Draw(Vector2 Position, SpriteBatch spritebatch, SpriteFont font, Texture2D Pixel)
//    {
//        DrawStatic(Position, spritebatch, font, Pixel, AlignRight, Text, Size, Color);
//    }

//    public static void DrawStatic(Vector2 Position, SpriteBatch spritebatch, SpriteFont font, Texture2D Pixel, bool AlignRight, string Text, Point Size, Color Color)
//    {
//        if (string.IsNullOrEmpty(Text)) return;

//        Vector2 origin = AlignRight ? new Vector2(Size.X, 0) : Vector2.Zero;
//        spritebatch.Draw(Pixel, new Rectangle((int)Position.X - 4 - (int)origin.X, (int)Position.Y - 4, Size.X, Size.Y), null, new Color(0, 0, 0, 100), 0, Vector2.Zero, SpriteEffects.None, 0.98f);
//        spritebatch.DrawString(font, Text, Position, Color, 0, origin, 1, SpriteEffects.None, 1);
//    }
//}

public delegate void LogEvent(string msg, Ward.LogType Type);
public class TwoInts
{
    public int One;
    public int Two;

    public TwoInts(int one, int two)
    {
        One = one;
        Two = two;
    }
}
public class TextColorValue
{
    public string Text;
    public Color Color;
    public float Value;
    public float Value2;
    public float Value3;

    public TextColorValue(string text, Color color, float value, float value2, float value3) { Text = text; Color = color; Value = value; Value3 = value2; Value3 = value3; }

    public void ChangeValue(float Target, float innertia)
    {
        Value += (Target - Value) * innertia;
    }
}

public struct Line3DLabeled
{
    public Vector3 Start;
    public Vector3 End;
    public Color Color;
    public string Label { get; set; }

    public Line3DLabeled(Vector3 start, Vector3 end, Color color) : this(start, end, color, null) { }
    public Line3DLabeled(Vector3 start, Vector3 end, Color color, string label)
        : this()
    {
        Start = start;
        End = end;
        Label = label;
        Color = color;
    }

    public void Draw(SpriteBatch sb, SpriteFont font, Matrix view, Matrix projection, GraphicsDevice gd, Texture2D Pixel, float thickness)
    {
        Vector3 start = gd.Viewport.Project(Start, projection, view, Matrix.Identity);
        Vector3 end = gd.Viewport.Project(End, projection, view, Matrix.Identity);
        Vector2 diff = new Vector2(end.X - start.X, end.Y - start.Y);
        Vector2 pos = new Vector2(start.X, start.Y);
        sb.Draw(Pixel, pos, null, Color, (float)Math.Atan2(diff.Y, diff.X), Vector2.Zero, new Vector2(Vector2.Distance(Vector2.Zero, diff), thickness), SpriteEffects.None, 1);
        if (!string.IsNullOrEmpty(Label))
            sb.DrawString(font, Label, pos, Color);
    }
}
public struct Point3DLabeled
{
    public Vector3 Position;
    public Color Color;
    public string Label { get; set; }

    public Point3DLabeled(Vector3 position, Color color) : this(position, color, null) { }
    public Point3DLabeled(Vector3 position, Color color, string label)
        : this()
    {
        Position = position;
        Color = color;
        Label = label;
    }

    public void Draw(SpriteBatch sb, SpriteFont font, Matrix view, Matrix projection, GraphicsDevice gd, Texture2D Pixel, float size)
    {
        Vector3 start = gd.Viewport.Project(Position, projection, view, Matrix.Identity);
        Vector2 pos = new Vector2(start.X, start.Y);
        sb.Draw(Pixel, pos, null, Color, 0, Vector2.Zero, new Vector2(size, size), SpriteEffects.None, 1);
        if (!string.IsNullOrEmpty(Label))
            sb.DrawString(font, Label, pos, Color);
    }
}
public struct LineLabeled
{
    public Vector2 Start { get; private set; }
    public Vector2 End { get; private set; }
    public float Rotation { get; private set; }
    public float Length { get; private set; }
    public Color Color { get; set; }
    public string Label { get; set; }

    public LineLabeled(Vector2 start, Vector2 end, Color color) : this(start, end, color, null)
    {
    }
    public LineLabeled(Vector2 start, Vector2 end, Color color, string label)
        : this()
    {
        Start = start;
        End = end;
        Vector2 diff = new Vector2(end.X - start.X, end.Y - start.Y);
        Rotation = (float)Math.Atan2(diff.Y, diff.X);
        Length = Vector2.Distance(start, end);
        Color = color;
        Label = label;
    }

    public void Draw(SpriteBatch sb, SpriteFont font, Texture2D Pixel, float thickness)
    {
        sb.Draw(Pixel, Start, null, Color, Rotation, Vector2.Zero, new Vector2(Length, thickness), SpriteEffects.None, 0);
        if (!string.IsNullOrEmpty(Label))
            sb.DrawString(font, Label, Start, Color);
    }
}
public struct BoxLabeled
{
    public Vector2 Position { get; private set; }
    public Vector2 Size { get; private set; }
    public float Rotation { get; private set; }
    public Color Color { get; set; }
    public string Label { get; set; }

    public BoxLabeled(Vector2 pos, Vector2 size, Color color)
        : this(pos, size, color, 0, null)
    {
    }
    public BoxLabeled(Vector2 pos, Vector2 size, Color color, float rotation, string label) : this()
    {
        Position = pos;
        Size = size;
        Rotation = rotation;
        Color = color;
        Label = label;
    }

    public void Draw(SpriteBatch sb, SpriteFont font, Texture2D Pixel)
    {
        sb.Draw(Pixel, Position, null, Color, Rotation, Vector2.Zero, Size, SpriteEffects.None, 1);
        if (!string.IsNullOrEmpty(Label))
            sb.DrawString(font, Label, Position, Color);
    }
}
public struct PointLabeled
{
    public Vector2 Position { get; private set; }
    public float Rotation { get; private set; }
    public Color Color { get; set; }
    public string Label { get; set; }

    public PointLabeled(Vector2 pos, Color color)
        : this(pos, color, 0, null)
    {
    }
    public PointLabeled(Vector2 pos, Color color, float rotation, string label)
        : this()
    {
        Position = pos;
        Rotation = rotation;
        Color = color;
        Label = label;
    }

    public void Draw(SpriteBatch sb, SpriteFont font, Texture2D Pixel, float size)
    {
        sb.Draw(Pixel, Position, null, Color, Rotation, Vector2.Zero, size, SpriteEffects.None, 1);
        if (!string.IsNullOrEmpty(Label))
            sb.DrawString(font, Label, Position, Color);
    }
}

#endregion