using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using System.IO;

// These are some simple helper datatypes I've created as part of personal code libraries previously or found on the internet

public struct AABB
{
    public Vector2 Min;
    public Vector2 Max;

    public float Left { get { return Min.X; } }
    public float Right { get { return Max.X; } }
    public float Top { get { return Min.Y; } }
    public float Bottom { get { return Max.Y; } }
    public float Width { get { return Max.X - Min.X; } }
    public float Height { get { return Max.Y - Min.Y; } }
    public Vector2 Size { get { return new Vector2(Max.X - Min.X, Max.Y - Min.Y); } }
    public float X { get { return Min.X; } }
    public float Y { get { return Min.Y; } }

    public AABB(Vector2 pos, Vector2 size)
    {
        Min = pos;
        Max = pos + size;
    }
    public AABB(float x, float y, float W, float H)
    {
        Min = new Vector2(x, y);
        Max = new Vector2(x + W, y + H);
    }

    public bool Contains(Vector2 point)
    {
        if (point.X < Left) return false;
        if (point.X > Right) return false;
        if (point.Y < Top) return false;
        if (point.Y > Bottom) return false;
        return true;
    }
    public bool Contains(AABB rectangle)
    {
        if (rectangle.Left < Left) return false;
        if (rectangle.Right > Right) return false;
        if (rectangle.Top < Top) return false;
        if (rectangle.Bottom > Bottom) return false;
        return true;
    }
    public bool Contains(Rectangle rectangle)
    {
        if (rectangle.Left < Left) return false;
        if (rectangle.Right > Right) return false;
        if (rectangle.Top < Top) return false;
        if (rectangle.Bottom > Bottom) return false;
        return true;
    }

    public bool Intersects(Vector2 point)
    {
        if (point.X <= Left) return false;
        if (point.X >= Right) return false;
        if (point.Y <= Top) return false;
        if (point.Y >= Bottom) return false;
        return true;
    }
    public bool Intersects(AABB rectangle)
    {
        if (rectangle.Right <= Left) return false;
        if (rectangle.Left >= Right) return false;
        if (rectangle.Bottom <= Top) return false;
        if (rectangle.Top >= Bottom) return false;
        return true;
    }
    public bool Intersects(Rectangle rectangle)
    {
        if (rectangle.Right <= Left) return false;
        if (rectangle.Left >= Right) return false;
        if (rectangle.Bottom <= Top) return false;
        if (rectangle.Top >= Bottom) return false;
        return true;
    }

    public static implicit operator Rectangle(AABB value)
    {
        return new Rectangle((int)value.Min.X, (int)value.Min.Y, (int)(value.Max.X - value.Min.X), (int)(value.Max.Y - value.Min.Y));
    }
}

public struct Point2 : IComparable, IEqualityComparer<Point2>, IEquatable<Point2>
{
    public static readonly Point2 Zero = new Point2();
    public static readonly Point2 One = new Point2(1, 1);
    public static readonly Point2 MinusOne = new Point2(-1, -1);
    public static readonly Point2 Null = new Point2(int.MaxValue, int.MinValue);

    public int X;
    public int Y;

    public Point2(int xy)
    {
        X = xy;
        Y = xy;
    }
    public Point2(int x, int y)
    {
        X = x; Y = y;
    }

    public static Point2 operator *(Point2 a, Point2 b)
    {
        return new Point2(a.X * b.X, a.Y * b.Y);
    }
    public static Vector2 operator *(Point2 a, Vector2 b)
    {
        return new Vector2(a.X * b.X, a.Y * b.Y);
    }
    public static Vector2 operator *(Point2 a, float b)
    {
        return new Vector2(a.X * b, a.Y * b);
    }
    public static Point2 operator +(Point2 a, Point2 b)
    {
        return new Point2(a.X + b.X, a.Y + b.Y);
    }
    public static Point2 operator -(Point2 a, Point2 b)
    {
        return new Point2(a.X - b.X, a.Y - b.Y);
    }
    public static Point2 operator -(Point2 a)
    {
        return new Point2(-a.X, -a.Y);
    }
    public static Point2 operator *(Point2 a, int b)
    {
        return new Point2(a.X * b, a.Y * b);
    }
    public static Point2 operator /(Point2 a, int b)
    {
        return new Point2(a.X / b, a.Y / b);
    }
    public static Point2 operator /(Point2 a, Point2 b)
    {
        return new Point2(a.X / b.X, a.Y / b.Y);
    }
    public static Vector2 operator /(Point2 a, float b)
    {
        return new Vector2(a.X / b, a.Y / b);
    }
    public static bool operator ==(Point2 a, Point2 b)
    {
        return a.X == b.X && a.Y == b.Y;
    }
    public static bool operator !=(Point2 a, Point2 b)
    {
        return a.X != b.X || a.Y != b.Y;
    }

    public static int Manhattan(Point2 a, Point2 b)
    {
        return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y);
    }

#if SYSDRAW
    public static implicit operator System.Drawing.Point(Point2 a)
    {
        return new System.Drawing.Point(a.X, a.Y);
    }
    public static implicit operator Point2(System.Drawing.Point a)
    {
        return new Point2(a.X, a.Y);
    }
#endif
    public static implicit operator Microsoft.Xna.Framework.Point(Point2 a)
    {
        return new Microsoft.Xna.Framework.Point(a.X, a.Y);
    }
    public static implicit operator Point2(Microsoft.Xna.Framework.Point a)
    {
        return new Point2(a.X, a.Y);
    }

    public static implicit operator Vector2(Point2 a)
    {
        return new Vector2(a.X, a.Y);
    }

    public static explicit operator Point2(Vector2 a)
    {
        return new Point2((int)a.X, (int)a.Y);
    }

    public static explicit operator Vector3(Point2 a)
    {
        return new Vector3(a.X, a.Y, 0);
    }
    public static explicit operator Point2(Vector3 a)
    {
        return new Point2((int)Math.Floor(a.X), (int)Math.Floor(a.Y));
    }
    public static Point2 RoundV2(Vector2 vector)
    {
        return new Point2((int)Math.Round(vector.X, 0), (int)Math.Round(vector.Y, 0));
    }

    public override string ToString()
    {
        return string.Concat("{ ", X, ", ", Y, " }");
    }
    public string ToString(string format)
    {
        return string.Concat("{ ", X.ToString(format), ", ", Y.ToString(format), " }");
    }

    public int CompareTo(object obj)
    {
        return X.CompareTo(obj);
    }

    public bool Equals(Point2 x, Point2 y)
    {
        return x == y;
    }

    public int GetHashCode(Point2 obj)
    {
        return X.GetHashCode() + Y.GetHashCode();
    }

    public bool Equals(Point2 other)
    {
        return this == other;
    }
}

public static class Data
{
    public const string ExtMap = ".jmp";
    public const string ExtLocalisation = ".loc";

    static public Dictionary<string, Texture2D> Texture = new Dictionary<string, Texture2D>();
    static public Dictionary<string, SpriteFont> Font = new Dictionary<string, SpriteFont>();
    static public Dictionary<string, Effect> Effect = new Dictionary<string, Effect>();
    static public Dictionary<string, SoundEffect> Sfx = new Dictionary<string, SoundEffect>();

    public static void LoadAll(ContentManager content)
    {
        string[] files = Directory.GetFiles(content.RootDirectory, "*.xnb", SearchOption.AllDirectories);
        LoadFilesFromArray(files, content);
    }

    private static void LoadFilesFromArray(string[] files, ContentManager content)
    {
        foreach (string file in files)
        {
            string relativePath = file.Substring(file.IndexOf(@"Content\") + @"Content\".Length);
            relativePath = relativePath.Remove(relativePath.LastIndexOf(".xnb"), ".xnb".Length);

            object o = content.Load<object>(relativePath);

            try
            {
                if (o is Texture2D)
                    Texture.Add(relativePath.Substring(relativePath.LastIndexOf('\\') + 1).ToLower(), o as Texture2D);
                else if (o is SpriteFont)
                    Font.Add(relativePath.Substring(relativePath.LastIndexOf('\\') + 1).ToLower(), o as SpriteFont);
                else if (o is Effect)
                    Effect.Add(relativePath.Substring(relativePath.LastIndexOf('\\') + 1).ToLower(), o as Effect);
                else if (o is SoundEffect)
                    Sfx.Add(relativePath.Substring(relativePath.LastIndexOf('\\') + 1).ToLower(), o as SoundEffect);

            }
            catch (Exception e)
            {
#if Ward
                Ward.Log("Error loading .xnb file. Unknown object type: " + e.ToString());
#endif
            }
        }
    }
}

[Flags]
public enum Direction
{
    None = 0,
    East = 1,
    West = 2,
    North = 4,
    South = 8,
    NorthWest = 16,
    NorthEast = 32,
    SouthWest = 64,
    SouthEast = 128,
    All = 256,
}

delegate void Particle2DEvent(Particle2D p);
delegate void Emitter2DEvent(Emitter2D e);
class Particle2DSystem
{
    public List<Emitter2D> Emitters = new List<Emitter2D>();

    public string TexturePath { get; private set; }
    public Texture2D Texture { get; private set; }
    public int ActiveParticles { get { return particles.Count; } }
    public float DrawDepth { get; set; }
    public Particle2DEvent OnUpdateParticle;
    public Particle2DEvent OnInitializeParticle;
    public Emitter2DEvent OnUpdateEmitter;

    List<Particle2D> particles = new List<Particle2D>();
    Stack<Particle2D> pool = new Stack<Particle2D>();

    public Particle2DSystem(string textureName)
    {
        TexturePath = textureName;
        DrawDepth = 0.5f;
    }

    public void LoadContent(ContentManager content)
    {
        Texture = content.Load<Texture2D>(TexturePath);
    }

    public void Update(float elapsedTime)
    {
        foreach (var item in Emitters)
        {
            if (OnUpdateEmitter != null)
                OnUpdateEmitter(item);
            item.Update(elapsedTime, this);
        }

        for (int i = particles.Count - 1; i >= 0; i--)
        {
            var p = particles[i];
            p.Update(elapsedTime, this);
            if (p.Life >= p.LifeTime)
            {
                pool.Push(p);
                particles.RemoveAt(i);
            }
        }
    }

    public void Draw(SpriteBatch sb)
    {
        float d = DrawDepth;
        foreach (var item in particles)
            item.Draw(sb, this);
        DrawDepth = d;
    }

    public virtual Particle2D SpawnParticle(Emitter2D emitter)
    {
        Particle2D p;
        if (pool.Count > 0)
            p = pool.Pop();
        else
            p = new Particle2D();

        particles.Add(p);
        emitter.InitializeParticle(p);
        p.Emitter = emitter;
        if (OnInitializeParticle != null)
            OnInitializeParticle(p);

        return p;
    }

}

public class Pool<T> : Stack<T> where T : new()
{
    public Pool()
    {
    }

    public Pool(int size)
    {
        for (int i = 0; i < size; i++)
            Push(new T());
    }

    public T Fetch()
    {
        if (Count > 0)
            return Pop();
        return new T();
    }
}

class Particle2D
{
    public Emitter2D Emitter;
    public Rectangle? TexSource;
    public Vector2 Position;
    public Color Color = Color.White;
    public Vector2 Offset;
    public Vector2 Velocity;
    public Vector2 Scale = Vector2.One;
    public float Rotation;
    public float Life;
    public float LifeTime;
    public object Tag;

    public virtual void Update(float elapsedTime, Particle2DSystem sys)
    {
        Position += Velocity;
        Life += elapsedTime;
        //Velocity.X *= 0.995f;
        //if (Position.Y <= 0)
        //    Life = 99999;
        if (sys.OnUpdateParticle != null)
            sys.OnUpdateParticle(this);
    }

    public virtual void Draw(SpriteBatch sb, Particle2DSystem sys)
    {
        sb.Draw(sys.Texture, new Vector2((int)Position.X, (int)Position.Y), TexSource, Color, Rotation, Offset, Scale, SpriteEffects.None, sys.DrawDepth += 0.00000001f);
    }
}

class Emitter2D
{
    public static Pool<Emitter2D> Pool = new Pool<Emitter2D>();

    public float SpawnPerSecond = 10;
    public bool SpawnEnable = true;
    public float LifeTimeMin = 1;
    public float LifeTimeMax = 2;
    public Vector2 VelocityMin = new Vector2(-1, -1);
    public Vector2 VelocityMax = new Vector2(1, 1);
    public Rectangle? TexSource;
    public WeightedList<Rectangle> TexSources;
    public Vector2 EmitterPosition;
    public Vector2 EmitterArgs;
    public Color Color = Color.White;
    public object Tag;
    public float EnableTime;
    public float DisableTime;

    public enum EmitterType { Rectangle, Point, Circle, Line }
    public EmitterType Type { get; private set; }
    float spawnDebt;

    public Emitter2D()
    {

    }

    public void Define(float x, float y, float width, float height)
    {
        EmitterPosition = new Vector2(x, y);
        EmitterArgs = new Vector2(width, height);
        Type = Emitter2D.EmitterType.Rectangle;
    }
    public void Define(Vector2 point)
    {
        EmitterPosition = point;
        Type = EmitterType.Point;
    }
    public void Define(Vector2 circle, float radius)
    {
        EmitterPosition = circle;
        EmitterArgs = new Vector2(radius, radius);
        Type = EmitterType.Circle;
    }
    public void Define(Vector2 lineA, Vector2 lineB)
    {
        EmitterPosition = lineA;
        EmitterArgs = lineB;
        Type = EmitterType.Line;
    }

    public Vector2 GetSpawnPosition()
    {
        switch (Type)
        {
            case EmitterType.Rectangle:
                return new Vector2(EmitterPosition.X + Tools.Randomize.Float(EmitterArgs.X), EmitterPosition.Y + Tools.Randomize.Float(EmitterArgs.Y));

            case EmitterType.Circle:
                float rot = Tools.Randomize.Float(MathHelper.TwoPi);
                return new Vector2(EmitterPosition.X + (float)Math.Sin(rot) * EmitterArgs.X, EmitterPosition.Y + (float)Math.Cos(rot) * EmitterArgs.Y);

            case EmitterType.Line:
                float len = Tools.Randomize.Float();
                return new Vector2(EmitterPosition.X + len * EmitterArgs.X, EmitterPosition.Y + len * EmitterArgs.Y);

            default:
                return EmitterPosition;
        }
    }

    public void Update(float elapsedSec, Particle2DSystem sys)
    {
        if (SpawnEnable)
        {
            EnableTime += elapsedSec;
            DisableTime = 0;
            spawnDebt += SpawnPerSecond * elapsedSec;
            int spawns = (int)spawnDebt;
            if (spawns > 0)
            {
                spawnDebt -= spawns;
                for (int i = 0; i < spawns; i++)
                    sys.SpawnParticle(this);
            }
        }
        else
        {
            EnableTime = 0;
            DisableTime += elapsedSec;
        }
    }

    public void InitializeParticle(Particle2D p)
    {
        p.Position = GetSpawnPosition();
        p.Life = 0;
        p.LifeTime = Tools.Randomize.Float(LifeTimeMin, Math.Max(LifeTimeMin, LifeTimeMax));
        p.Rotation = 0;
        p.Velocity = new Vector2(Tools.Randomize.Float(VelocityMin.X, VelocityMax.X), Tools.Randomize.Float(VelocityMin.Y, VelocityMax.Y));
        p.Color = Color;
        if (TexSources == null)
            p.TexSource = TexSource;
        else
            p.TexSource = TexSources.RandomElement();
    }
}

/// <summary>
/// A simple implementation of a list with weights that can give a random element based on weight. 
/// Fast lookups in exchange for larger memory footprint based on precision. 
/// Precision must be atleast number of objects in list, but should be higher to allow differences in weight.
/// Add all elements at the same time as re-weighting may be costly.
/// </summary>
/// <typeparam name="T"></typeparam>
public class WeightedList<T>
{
    public int EnabledElements { get; private set; }
    public int Count { get { return elements.Count; } }
    List<WeightedElement> elements;
    WeightedElement[] table;

    static Random rnd = new Random();

    T singleEnabled;

    public WeightedList(int precision = 100)
    {
        table = new WeightedElement[precision];
    }

    public void SetElements(List<WeightedElement> elements)
    {
        this.elements = elements;
        if (elements.Count > table.Length) throw new Exception("Elements contains more objects than current precision can handle!");

        //Normalize weights
        float total = 0;
        foreach (var item in elements)
            total += item.Weight;
        if (total <= 0)
            throw new Exception("No element has any weight!");
        foreach (var item in elements)
            item.Weight /= total;

        total = 0;
        int from, to = 0;
        foreach (var item in elements)
        {
            total += item.Weight;
            from = to;
            to = (int)(total * table.Length);
            for (int i = from; i < to; i++)
                table[i] = item;
        }
        calcEnabled();
    }

    public void RecalculateWeights()
    {
        SetElements(elements);
    }

    public void Add(WeightedElement element, bool recalculateWeights = true)
    {
        if (elements == null)
            elements = new List<WeightedElement>();
        elements.Add(element);
        if (recalculateWeights)
            SetElements(elements);
    }

    public void Clear()
    {
        if (elements != null)
            elements.Clear();
        EnabledElements = 0;
    }

    public T RandomElement()
    {
        if (EnabledElements == 0) return default(T);
        if (EnabledElements == 1) return singleEnabled;
        if (elements.Count == 1) return elements[0].Value;

        var e = elements[rnd.Next(elements.Count)];
        if (e.Enabled)
            return e.Value;
        return RandomElement();
    }

    public void SetEnable(int index, bool value)
    {
        elements[index].Enabled = value;
        calcEnabled();
    }

    private void calcEnabled()
    {
        int count = 0;
        singleEnabled = default(T);
        foreach (var item in elements)
            if (item.Enabled)
            {
                count++;
                singleEnabled = item.Value;
            }
        EnabledElements = count;
    }

    public class WeightedElement
    {
        public WeightedElement(T value, float weight = 1)
        {
            Value = value;
            Weight = weight;
        }
        public T Value { get; private set; }
        public float Weight { get; internal set; }
        public bool Enabled = true;
    }
}

// This perlin class was found on the interwebs
/// <summary>
/// This is a class able to generate pseudo coherent random noise based on Ken Perlin's algorithm
/// <see cref="http://mrl.nyu.edu/~perlin/noise/"/>
/// <seealso cref="http://www.siafoo.net/snippet/144"/>
/// </summary>
public sealed class Perlin
{
    #region Constants

    /// <summary>
    /// Stores the size of the random value array
    /// </summary>
    private int RANDOM_SIZE = 256;

    #endregion

    #region Fields

    /// <summary>
    /// Stores the random values used to generate the noise
    /// </summary>
    private readonly int[] values;

    public readonly int Seed;

    #endregion

    /// <summary>
    /// Initializes a new instance of the PerlinNoise class.
    /// </summary>
    /// <param name="seed">The seed used to generate the random values</param>
    public Perlin(int seed)
    {
        Seed = seed;
        // Initialize the random values array
        this.values = new int[RANDOM_SIZE * 2];

        // Initialize the random generator
        Random generator = new Random(seed);

        // Initialize an array for storing 256 random values
        byte[] source = new byte[RANDOM_SIZE];

        // Generate 256 random byte values
        generator.NextBytes(source);

        // Copy the source data twice to the generator array
        for (int i = 0; i < RANDOM_SIZE; i++)
            this.values[i + RANDOM_SIZE] = this.values[i] = source[i];
    }

    /// <summary>
    /// Generates a one-dimensional noise
    /// </summary>
    /// <param name="x">The entry value for the noise</param>
    /// <returns>A value between [-1;1] representing the noise amount</returns>
    public float Noise(float x)
    {
        // Compute the cell coordinates
        int X = (int)Math.Floor(x) & 255;

        // Retrieve the decimal part of the cell
        x -= (float)Math.Floor(x);

        float u = Fade(x);

        return Lerp(u, Grad(this.values[X], x), Grad(this.values[X + 1], x - 1));
    }

    /// <summary>
    /// Generates a bi-dimensional noise
    /// </summary>
    /// <param name="x">The X entry value for the noise</param>
    /// <param name="y">The Y entry value for the noise</param>
    /// <returns>A value between [-1;1] representing the noise amount</returns>
    public float Noise(float x, float y)
    {
        // Compute the cell coordinates
        int X = (int)Math.Floor(x) & 255;
        int Y = (int)Math.Floor(y) & 255;

        // Retrieve the decimal part of the cell
        x -= (float)Math.Floor(x);
        y -= (float)Math.Floor(y);

        // Smooth the curve
        float u = Fade(x);
        float v = Fade(y);

        // Fetch some randoms values from the table
        int A = this.values[X] + Y;
        int B = this.values[X + 1] + Y;

        // Interpolate between directions 
        return Lerp(v, Lerp(u, Grad(this.values[A], x, y),
                                Grad(this.values[B], x - 1, y)),
                        Lerp(u, Grad(this.values[A + 1], x, y - 1),
                                Grad(this.values[B + 1], x - 1, y - 1)));
    }

    /// <summary>
    /// Generates a tri-dimensional noise
    /// </summary>
    /// <param name="x">The X entry value for the noise</param>
    /// <param name="y">The Y entry value for the noise</param>
    /// <param name="z">The Z entry value for the noise</param>
    /// <returns>A value between [-1;1] representing the noise amount</returns>
    public float Noise(float x, float y, float z)
    {
        // Compute the cell coordinates
        int X = (int)Math.Floor(x) & 255;
        int Y = (int)Math.Floor(y) & 255;
        int Z = (int)Math.Floor(z) & 255;

        // Retrieve the decimal part of the cell
        x -= (float)Math.Floor(x);
        y -= (float)Math.Floor(y);
        z -= (float)Math.Floor(z);

        // Smooth the curve
        float u = Fade(x);
        float v = Fade(y);
        float w = Fade(z);

        // Fetch some randoms values from the table
        int A = this.values[X] + Y;
        int AA = this.values[A] + Z;
        int AB = this.values[A + 1] + Z;
        int B = this.values[X + 1] + Y;
        int BA = this.values[B] + Z;
        int BB = this.values[B + 1] + Z;

        // Interpolate between directions
        return Lerp(w, Lerp(v, Lerp(u, Grad(this.values[AA], x, y, z),
                                        Grad(this.values[BA], x - 1, y, z)),
                                Lerp(u, Grad(this.values[AB], x, y - 1, z),
                                        Grad(this.values[BB], x - 1, y - 1, z))),
                        Lerp(v, Lerp(u, Grad(this.values[AA + 1], x, y, z - 1),
                                        Grad(this.values[BA + 1], x - 1, y, z - 1)),
                                Lerp(u, Grad(this.values[AB + 1], x, y - 1, z - 1),
                                        Grad(this.values[BB + 1], x - 1, y - 1, z - 1))));
    }

    #region Private helpers methods

    /// <summary>
    /// Smooth the entry value
    /// </summary>
    /// <param name="t">The entry value</param>
    /// <returns>The smoothed value</returns>
    private static float Fade(float t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    /// <summary>
    /// Interpolate linearly between A and B.
    /// </summary>
    /// <param name="t">The amount of the interpolation</param>
    /// <param name="a">The starting value</param>
    /// <param name="b">The ending value</param>
    /// <returns>The interpolated value between A and B</returns>
    private static float Lerp(float t, float a, float b)
    {
        return a + t * (b - a);
    }

    /// <summary>
    /// Modifies the result by adding a directional bias
    /// </summary>
    /// <param name="hash">The random value telling in which direction the bias will occur</param>
    /// <param name="x">The amount of the bias on the X axis</param>
    /// <returns>The directional bias strength</returns>
    private static float Grad(int hash, float x)
    {
        // Result table
        // ---+------+----
        //  0 | 0000 |  x 
        //  1 | 0001 | -x 

        return (hash & 1) == 0 ? x : -x;
    }

    /// <summary>
    /// Modifies the result by adding a directional bias
    /// </summary>
    /// <param name="hash">The random value telling in which direction the bias will occur</param>
    /// <param name="x">The amount of the bias on the X axis</param>
    /// <param name="y">The amount of the bias on the Y axis</param>
    /// <returns>The directional bias strength</returns>
    private static float Grad(int hash, float x, float y)
    {
        // Fetch the last 3 bits
        int h = hash & 3;

        // Result table for U
        // ---+------+---+------
        //  0 | 0000 | x |  x
        //  1 | 0001 | x |  x
        //  2 | 0010 | x | -x
        //  3 | 0011 | x | -x

        float u = (h & 2) == 0 ? x : -x;

        // Result table for V
        // ---+------+---+------
        //  0 | 0000 | y |  y
        //  1 | 0001 | y | -y
        //  2 | 0010 | y |  y
        //  3 | 0011 | y | -y

        float v = (h & 1) == 0 ? y : -y;

        // Result table for U + V
        // ---+------+----+----+--
        //  0 | 0000 |  x |  y |  x + y
        //  1 | 0001 |  x | -y |  x - y
        //  2 | 0010 | -x |  y | -x + y
        //  3 | 0011 | -x | -y | -x - y

        return u + v;
    }

    /// <summary>
    /// Modifies the result by adding a directional bias
    /// </summary>
    /// <param name="hash">The random value telling in which direction the bias will occur</param>
    /// <param name="x">The amount of the bias on the X axis</param>
    /// <param name="y">The amount of the bias on the Y axis</param>
    /// <param name="z">The amount of the bias on the Z axis</param>
    /// <returns>The directional bias strength</returns>
    private static float Grad(int hash, float x, float y, float z)
    {
        // Fetch the last 4 bits
        int h = hash & 15;

        // Result table for U
        // ---+------+---+------
        //  0 | 0000 | x |  x
        //  1 | 0001 | x | -x
        //  2 | 0010 | x |  x
        //  3 | 0011 | x | -x
        //  4 | 0100 | x |  x
        //  5 | 0101 | x | -x
        //  6 | 0110 | x |  x
        //  7 | 0111 | x | -x
        //  8 | 1000 | y |  y
        //  9 | 1001 | y | -y
        // 10 | 1010 | y |  y
        // 11 | 1011 | y | -y
        // 12 | 1100 | y |  y
        // 13 | 1101 | y | -y
        // 14 | 1110 | y |  y
        // 15 | 1111 | y | -y

        float u = h < 8 ? x : y;

        // Result table for V
        // ---+------+---+------
        //  0 | 0000 | y |  y
        //  1 | 0001 | y |  y
        //  2 | 0010 | y | -y
        //  3 | 0011 | y | -y
        //  4 | 0100 | z |  z
        //  5 | 0101 | z |  z
        //  6 | 0110 | z | -z
        //  7 | 0111 | z | -z
        //  8 | 1000 | z |  z
        //  9 | 1001 | z |  z
        // 10 | 1010 | z | -z
        // 11 | 1011 | z | -z
        // 12 | 1100 | x |  x
        // 13 | 1101 | z |  z
        // 14 | 1110 | x | -x
        // 15 | 1111 | z | -z

        float v = h < 4 ? y : h == 12 || h == 14 ? x : z;

        // Result table for U+V
        // ---+------+----+----+-------
        //  0 | 0000 |  x |  y |  x + y
        //  1 | 0001 | -x |  y | -x + y
        //  2 | 0010 |  x | -y |  x - y
        //  3 | 0011 | -x | -y | -x - y
        //  4 | 0100 |  x |  z |  x + z
        //  5 | 0101 | -x |  z | -x + z
        //  6 | 0110 |  x | -z |  x - z
        //  7 | 0111 | -x | -z | -x - z
        //  8 | 1000 |  y |  z |  y + z
        //  9 | 1001 | -y |  z | -y + z
        // 10 | 1010 |  y | -z |  y - z
        // 11 | 1011 | -y | -z | -y - z

        // The four last results already exists in the table before
        // They are doubled because you must get a result for all
        // 4-bit combinaisons values.

        // 12 | 1100 |  y |  x |  y + x
        // 13 | 1101 | -y |  z | -y + z
        // 14 | 1110 |  y | -x |  y - x
        // 15 | 1111 | -y | -z | -y - z

        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
    }

    #endregion
}

namespace Tools
{

    #region Randomize

    public static class Randomize
    {
        public static Random Random = new Random();

        public static bool Bool()
        {
            return Random.Next(2) == 0;
        }

        public static int Int()
        {
            return Random.Next();
        }
        public static int Int(int maxValue)
        {
            return Random.Next(maxValue);
        }
        public static int Int(int minValue, int maxValue)
        {
            return Random.Next(minValue, maxValue);
        }

        public static double Double()
        {
            return Random.NextDouble();
        }

        public static float Float()
        {
            return (float)Double();
        }
        public static float Float(float maxValue)
        {
            return Float() * maxValue;
        }
        public static float Float(float minValue, float maxValue)
        {
            return Float(maxValue - minValue) + minValue;
        }

        public static Color Color()
        {
            return new Color((byte)(Int(0, 255)),
                                (byte)(Int(0, 255)),
                                (byte)(Int(0, 255)));
        }
        public static Color Color(int minValue, int maxValue)
        {
            return new Color((byte)(Int(minValue, maxValue)),
                                (byte)(Int(minValue, maxValue)),
                                (byte)(Int(minValue, maxValue)));
        }
        public static Color Color(int minValue, int maxValue, int minHighValue)
        {
            Color C = Color(minValue, maxValue);

            if (C.R < minHighValue || C.G < minHighValue || C.B < minHighValue)
            {
                int iRnd = Int(1, 3);
                if (iRnd == 1)
                    C = new Color((byte)minHighValue, C.G, C.B);
                else if (iRnd == 2)
                    C = new Color(C.R, (byte)minHighValue, C.B);
                else if (iRnd == 3)
                    C = new Color(C.R, C.G, (byte)minHighValue);
            }
            return C;
        }

        public static Vector2 Vector2(float minValue, float maxValue)
        {
            return new Vector2(Float(minValue, maxValue), Float(minValue, maxValue));
        }

        public static Vector2 Vector2(float minX, float maxX, float minY, float maxY)
        {
            return new Vector2(Float(minX, maxX), Float(minY, maxY));
        }

        public static Vector3 Vector3(float minValue, float maxValue)
        {
            return new Vector3(Vector2(minValue, maxValue), Float(minValue, maxValue));
        }

        public static Vector3 Vector3D2(float minValue, float maxValue)
        {
            return new Vector3(Float(minValue, maxValue), 0, Float(minValue, maxValue));
        }

        public static Vector2 Vector2Direction()
        {
            Vector2 rnd = new Vector2((float)Random.NextDouble() * 2 - 1, (float)Random.NextDouble() * 2 - 1);
            rnd.Normalize();
            return rnd;
        }

        public static T FromArray<T>(T[] Array)
        {
            return Array[Int(Array.Length)];
        }

        public static T FromList<T>(List<T> List)
        {
            return List[Int(List.Count)];
        }

        public static char Letter()
        {
            return (char)('a' + Int(26));
        }

        // Random Extensinos

        public static float NextFloat(this Random rnd)
        {
            return (float)rnd.NextDouble();
        }
        public static float NextFloat(this Random rnd, float maxValue)
        {
            return (float)(rnd.NextDouble() * maxValue);
        }
        public static float NextFloat(this Random rnd, float minValue, float maxValue)
        {
            return (float)(minValue + rnd.NextDouble() * maxValue);
        }
        public static T FromArray<T>(this Random rnd, T[] Array)
        {
            return Array[rnd.Next(Array.Length)];
        }

        public static T FromList<T>(this Random rnd, List<T> List)
        {
            return List[rnd.Next(List.Count)];
        }

        public static char Letter(this Random rnd)
        {
            return (char)('a' + rnd.Next(26));
        }
    }
    #endregion
}

#region Bloom

/// <summary>
/// Class holds all the settings used to tweak the bloom effect.
/// </summary>
public class BloomSettings
{
    #region Fields


    // Name of a preset bloom setting, for display to the user.
    public readonly string Name;


    // Controls how bright a pixel needs to be before it will bloom.
    // Zero makes everything bloom equally, while higher values select
    // only brighter colors. Somewhere between 0.25 and 0.5 is good.
    public readonly float BloomThreshold;


    // Controls how much blurring is applied to the bloom image.
    // The typical range is from 1 up to 10 or so.
    public readonly float BlurAmount;


    // Controls the amount of the bloom and base images that
    // will be mixed into the final scene. Range 0 to 1.
    public readonly float BloomIntensity;
    public readonly float BaseIntensity;


    // Independently control the color saturation of the bloom and
    // base images. Zero is totally desaturated, 1.0 leaves saturation
    // unchanged, while higher values increase the saturation level.
    public readonly float BloomSaturation;
    public readonly float BaseSaturation;


    #endregion


    /// <summary>
    /// Constructs a new bloom settings descriptor.
    /// </summary>
    public BloomSettings(string name, float bloomThreshold, float blurAmount,
                         float bloomIntensity, float baseIntensity,
                         float bloomSaturation, float baseSaturation)
    {
        Name = name;
        BloomThreshold = bloomThreshold;
        BlurAmount = blurAmount;
        BloomIntensity = bloomIntensity;
        BaseIntensity = baseIntensity;
        BloomSaturation = bloomSaturation;
        BaseSaturation = baseSaturation;
    }


    /// <summary>
    /// Table of preset bloom settings, used by the sample program.
    /// </summary>
    public static BloomSettings[] PresetSettings =
        {
            //                Name           Thresh  Blur Bloom  Base  BloomSat BaseSat
            new BloomSettings("Default",     0.25f,  4,   1.25f, 1,    1,       1),
            new BloomSettings("Soft",        0,      3,   1,     1,    1,       1),
            new BloomSettings("Desaturated", 0.5f,   8,   2,     1,    0,       1),
            new BloomSettings("Saturated",   0.25f,  4,   2,     1,    2,       0),
            new BloomSettings("Blurry",      0,      2,   1,     0.1f, 1,       1),
            new BloomSettings("Subtle",      0.5f,   2,   1,     1,    1,       1),
        };
}

public class BloomComponent : DrawableGameComponent
{
    #region Fields

    SpriteBatch spriteBatch;

    Effect bloomExtractEffect;
    Effect bloomCombineEffect;
    Effect gaussianBlurEffect;

    public RenderTarget2D sceneRenderTarget;
    RenderTarget2D renderTarget1;
    RenderTarget2D renderTarget2;


    // Choose what display settings the bloom should use.
    public BloomSettings Settings
    {
        get { return settings; }
        set { settings = value; }
    }

    BloomSettings settings = BloomSettings.PresetSettings[0];


    // Optionally displays one of the intermediate buffers used
    // by the bloom postprocess, so you can see exactly what is
    // being drawn into each rendertarget.
    public enum IntermediateBuffer
    {
        PreBloom,
        BlurredHorizontally,
        BlurredBothWays,
        FinalResult,
    }

    public IntermediateBuffer ShowBuffer
    {
        get { return showBuffer; }
        set { showBuffer = value; }
    }

    IntermediateBuffer showBuffer = IntermediateBuffer.FinalResult;


    #endregion

    #region Initialization


    public BloomComponent(Game game)
        : base(game)
    {
        if (game == null)
            throw new ArgumentNullException("game");
    }


    /// <summary>
    /// Load your graphics content.
    /// </summary>
    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);

        bloomExtractEffect = Game.Content.Load<Effect>("BloomExtract");
        bloomCombineEffect = Game.Content.Load<Effect>("BloomCombine");
        gaussianBlurEffect = Game.Content.Load<Effect>("GaussianBlur");

        // Look up the resolution and format of our main backbuffer.
        PresentationParameters pp = GraphicsDevice.PresentationParameters;

        //int width = pp.BackBufferWidth;
        //int height = pp.BackBufferHeight;

        SurfaceFormat format = pp.BackBufferFormat;

        // Create a texture for rendering the main scene, prior to applying bloom.
        //sceneRenderTarget = new RenderTarget2D(GraphicsDevice, width, height, false,
        //                                       format, pp.DepthStencilFormat, pp.MultiSampleCount,
        //                                       RenderTargetUsage.DiscardContents);


        int width = Hover.Engine.GameRes.X;
        int height = Hover.Engine.GameRes.Y;

        // Create two rendertargets for the bloom processing. These are half the
        // size of the backbuffer, in order to minimize fillrate costs. Reducing
        // the resolution in this way doesn't hurt quality, because we are going
        // to be blurring the bloom images in any case.
        width /= 2;
        height /= 2;

        renderTarget1 = new RenderTarget2D(GraphicsDevice, width, height, false, format, DepthFormat.None);
        renderTarget2 = new RenderTarget2D(GraphicsDevice, width, height, false, format, DepthFormat.None);
    }


    /// <summary>
    /// Unload your graphics content.
    /// </summary>
    protected override void UnloadContent()
    {
        sceneRenderTarget.Dispose();
        renderTarget1.Dispose();
        renderTarget2.Dispose();
    }


    #endregion

    #region Draw


    /// <summary>
    /// This should be called at the very start of the scene rendering. The bloom
    /// component uses it to redirect drawing into its custom rendertarget, so it
    /// can capture the scene image in preparation for applying the bloom filter.
    /// </summary>
    public void BeginDraw()
    {
        if (Visible)
        {
            GraphicsDevice.SetRenderTarget(sceneRenderTarget);
        }
    }


    /// <summary>
    /// This is where it all happens. Grabs a scene that has already been rendered,
    /// and uses postprocess magic to add a glowing bloom effect over the top of it.
    /// </summary>
    public override void Draw(GameTime gameTime)
    {
        GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;

        // Pass 1: draw the scene into rendertarget 1, using a
        // shader that extracts only the brightest parts of the image.
        bloomExtractEffect.Parameters["BloomThreshold"].SetValue(
            Settings.BloomThreshold);

        DrawFullscreenQuad(sceneRenderTarget, renderTarget1,
                           bloomExtractEffect,
                           IntermediateBuffer.PreBloom);

        // Pass 2: draw from rendertarget 1 into rendertarget 2,
        // using a shader to apply a horizontal gaussian blur filter.
        SetBlurEffectParameters(1.0f / (float)renderTarget1.Width, 0);

        DrawFullscreenQuad(renderTarget1, renderTarget2,
                           gaussianBlurEffect,
                           IntermediateBuffer.BlurredHorizontally);

        // Pass 3: draw from rendertarget 2 back into rendertarget 1,
        // using a shader to apply a vertical gaussian blur filter.
        SetBlurEffectParameters(0, 1.0f / (float)renderTarget1.Height);

        DrawFullscreenQuad(renderTarget2, renderTarget1,
                           gaussianBlurEffect,
                           IntermediateBuffer.BlurredBothWays);

        // Pass 4: draw both rendertarget 1 and the original scene
        // image back into the main backbuffer, using a shader that
        // combines them to produce the final bloomed result.
        GraphicsDevice.SetRenderTarget(null);

        EffectParameterCollection parameters = bloomCombineEffect.Parameters;

        parameters["BloomIntensity"].SetValue(Settings.BloomIntensity);
        parameters["BaseIntensity"].SetValue(Settings.BaseIntensity);
        parameters["BloomSaturation"].SetValue(Settings.BloomSaturation);
        parameters["BaseSaturation"].SetValue(Settings.BaseSaturation);

        GraphicsDevice.Textures[1] = sceneRenderTarget;

        Viewport viewport = GraphicsDevice.Viewport;

        DrawFullscreenQuad(renderTarget1,
                           viewport.Width, viewport.Height,
                           bloomCombineEffect,
                           IntermediateBuffer.FinalResult);
    }


    /// <summary>
    /// Helper for drawing a texture into a rendertarget, using
    /// a custom shader to apply postprocessing effects.
    /// </summary>
    void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,
                            Effect effect, IntermediateBuffer currentBuffer)
    {
        GraphicsDevice.SetRenderTarget(renderTarget);

        DrawFullscreenQuad(texture,
                           renderTarget.Width, renderTarget.Height,
                           effect, currentBuffer);
    }


    /// <summary>
    /// Helper for drawing a texture into the current rendertarget,
    /// using a custom shader to apply postprocessing effects.
    /// </summary>
    void DrawFullscreenQuad(Texture2D texture, int width, int height,
                            Effect effect, IntermediateBuffer currentBuffer)
    {
        // If the user has selected one of the show intermediate buffer options,
        // we still draw the quad to make sure the image will end up on the screen,
        // but might need to skip applying the custom pixel shader.
        if (showBuffer < currentBuffer)
        {
            effect = null;
        }


        GraphicsDevice.Clear(Color.Black);

        //sb.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone);

        int w = Hover.Engine.graphics.GraphicsDevice.Viewport.Width;
        int h = Hover.Engine.graphics.GraphicsDevice.Viewport.Height;
        float scale = (float)Hover.Engine.graphics.GraphicsDevice.Viewport.Width / texture.Width;
        var rec = new Rectangle(0, h / 2 - (int)(texture.Height * scale / 2), w, (int)(texture.Height * scale));

        //sb.Draw(texture, rec, Color.White);

        //sb.End();


        //GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp;

        spriteBatch.Begin(0, BlendState.Opaque, SamplerState.PointClamp, null, null, effect);
        spriteBatch.Draw(texture, rec, Color.White);
        spriteBatch.End();
    }


    /// <summary>
    /// Computes sample weightings and texture coordinate offsets
    /// for one pass of a separable gaussian blur filter.
    /// </summary>
    void SetBlurEffectParameters(float dx, float dy)
    {
        // Look up the sample weight and offset effect parameters.
        EffectParameter weightsParameter, offsetsParameter;

        weightsParameter = gaussianBlurEffect.Parameters["SampleWeights"];
        offsetsParameter = gaussianBlurEffect.Parameters["SampleOffsets"];

        // Look up how many samples our gaussian blur effect supports.
        int sampleCount = weightsParameter.Elements.Count;

        // Create temporary arrays for computing our filter settings.
        float[] sampleWeights = new float[sampleCount];
        Vector2[] sampleOffsets = new Vector2[sampleCount];

        // The first sample always has a zero offset.
        sampleWeights[0] = ComputeGaussian(0);
        sampleOffsets[0] = new Vector2(0);

        // Maintain a sum of all the weighting values.
        float totalWeights = sampleWeights[0];

        // Add pairs of additional sample taps, positioned
        // along a line in both directions from the center.
        for (int i = 0; i < sampleCount / 2; i++)
        {
            // Store weights for the positive and negative taps.
            float weight = ComputeGaussian(i + 1);

            sampleWeights[i * 2 + 1] = weight;
            sampleWeights[i * 2 + 2] = weight;

            totalWeights += weight * 2;

            // To get the maximum amount of blurring from a limited number of
            // pixel shader samples, we take advantage of the bilinear filtering
            // hardware inside the texture fetch unit. If we position our texture
            // coordinates exactly halfway between two texels, the filtering unit
            // will average them for us, giving two samples for the price of one.
            // This allows us to step in units of two texels per sample, rather
            // than just one at a time. The 1.5 offset kicks things off by
            // positioning us nicely in between two texels.
            float sampleOffset = i * 2 + 1.5f;

            Vector2 delta = new Vector2(dx, dy) * sampleOffset;

            // Store texture coordinate offsets for the positive and negative taps.
            sampleOffsets[i * 2 + 1] = delta;
            sampleOffsets[i * 2 + 2] = -delta;
        }

        // Normalize the list of sample weightings, so they will always sum to one.
        for (int i = 0; i < sampleWeights.Length; i++)
        {
            sampleWeights[i] /= totalWeights;
        }

        // Tell the effect about our new filter settings.
        weightsParameter.SetValue(sampleWeights);
        offsetsParameter.SetValue(sampleOffsets);
    }


    /// <summary>
    /// Evaluates a single point on the gaussian falloff curve.
    /// Used for setting up the blur filter weightings.
    /// </summary>
    float ComputeGaussian(float n)
    {
        float theta = Settings.BlurAmount;

        return (float)((1.0 / Math.Sqrt(2 * Math.PI * theta)) *
                       Math.Exp(-(n * n) / (2 * theta * theta)));
    }


    #endregion
}

#endregion