using System;
using System.Runtime.InteropServices;

namespace Quik.CommandMachine
{
    [StructLayout(LayoutKind.Explicit)]
    public struct Frame
    {
        [FieldOffset(0)]
        private FrameType _type;

        [FieldOffset(sizeof(FrameType) + 0 * sizeof(int))]
        private int _i1;
        [FieldOffset(sizeof(FrameType) + 1 * sizeof(int))]
        private int _i2;
        [FieldOffset(sizeof(FrameType) + 2 * sizeof(int))]
        private int _i3;
        [FieldOffset(sizeof(FrameType) + 3 * sizeof(int))]
        private int _i4;

        [FieldOffset(sizeof(FrameType) + 0 * sizeof(float))]
        private float _f1;
        [FieldOffset(sizeof(FrameType) + 1 * sizeof(float))]
        private float _f2;
        [FieldOffset(sizeof(FrameType) + 2 * sizeof(float))]
        private float _f3;
        [FieldOffset(sizeof(FrameType) + 3 * sizeof(float))]
        private float _f4;

        [FieldOffset(24)]
        private object _object;

        public bool IsCommand => _type == FrameType.Command;
        public bool IsInteger =>
            _type == FrameType.IVec1 ||
            _type == FrameType.IVec2 ||
            _type == FrameType.IVec3 ||
            _type == FrameType.IVec4;
        public bool IsFloat =>
            _type == FrameType.Vec1 ||
            _type == FrameType.Vec2 ||
            _type == FrameType.Vec3 ||
            _type == FrameType.Vec4;

        public int VectorSize
        {
            get
            {
                switch (_type)
                {
                    case FrameType.None:
                        return 0;
                    default:
                        return 1;
                    case FrameType.Vec2: case FrameType.IVec2:
                        return 2;
                    case FrameType.Vec3: case FrameType.IVec3:
                        return 3;
                    case FrameType.Vec4: case FrameType.IVec4:
                        return 4;
                }
            }
        }

        public FrameType Type => _type;

        public int I1 => _i1;
        public int I2 => _i2;
        public int I3 => _i3;
        public int I4 => _i4;

        public float F1 => _f1;
        public float F2 => _f2;
        public float F3 => _f3;
        public float F4 => _f4;

        public static Frame None { get; } = new Frame() {
            _type = FrameType.None
        };

        #region Constructors
        public Frame(Command command) : this()
        {
            _type = FrameType.Command;
            _i1 = (int)command;
        }

        public Frame(object o)
        {
            _type = FrameType.Object;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _object = o;
        }

        public Frame(int i1)
        {
            _type = FrameType.IVec1;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _i1 = i1;
        }

        public Frame(int i1, int i2)
        {
            _type = FrameType.IVec2;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _i1 = i1;
            _i2 = i2;
        }

        public Frame(int i1, int i2, int i3)
        {
            _type = FrameType.IVec3;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _i1 = i1;
            _i2 = i2;
            _i3 = i3;
        }

        public Frame(int i1, int i2, int i3, int i4)
        {
            _type = FrameType.IVec4;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _i1 = i1;
            _i2 = i2;
            _i3 = i3;
            _i4 = i4;
        }

        public Frame(float f1)
        {
            _type = FrameType.Vec1;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _f1 = f1;
        }

        public Frame(float f1, float f2)
        {
            _type = FrameType.Vec2;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _f1 = f1;
            _f2 = f2;
        }

        public Frame(float f1, float f2, float f3)
        {
            _type = FrameType.Vec3;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _f1 = f1;
            _f2 = f2;
            _f3 = f3;
        }

        public Frame(float f1, float f2, float f3, float f4)
        {
            _type = FrameType.Vec4;

            _i1 = _i2 = _i3 = _i4 = default;
            _f1 = _f2 = _f3 = _f4 = default;
            _object = null;

            _f1 = f1;
            _f2 = f2;
            _f3 = f3;
            _f4 = f4;
        }

        #endregion

        public T As<T>()
        {
            return (T)_object;
        }

        public float GetF(int i)
        {
            switch (i)
            {
                case 0: return _f1;
                case 1: return _f2;
                case 2: return _f3;
                case 3: return _f4;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public int GetI(int i)
        {
            switch (i)
            {
                case 0: return _i1;
                case 1: return _i2;
                case 2: return _i3;
                case 3: return _i4;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        #region Frame->T Conversion

        public static explicit operator int(in Frame frame)
        {
            switch (frame.Type)
            {
                default:
                    throw new InvalidCastException();
                case FrameType.Command:
                case FrameType.IVec1:
                case FrameType.IVec2:
                case FrameType.IVec3:
                case FrameType.IVec4:
                    return frame._i1;
                case FrameType.Vec1:
                case FrameType.Vec2:
                case FrameType.Vec3:
                case FrameType.Vec4:
                    return (int)frame._f1;
            }

        }

        public static explicit operator float(in Frame frame)
        {
            switch (frame.Type)
            {
                default:
                    throw new InvalidCastException();
                case FrameType.IVec1:
                case FrameType.IVec2:
                case FrameType.IVec3:
                case FrameType.IVec4:
                    return frame._i1;
                case FrameType.Vec1:
                case FrameType.Vec2:
                case FrameType.Vec3:
                case FrameType.Vec4:
                    return frame._f1;
            }
        }

        public static explicit operator Command(in Frame frame)
        {
            if (frame.Type != FrameType.Command)
            {
                throw new InvalidCastException("Not a command frame.");
            }

            return (Command)frame._i1;
        }
        
        public static explicit operator QVec2(in Frame frame)
        {
            switch (frame.Type)
            {
                default:
                    throw new InvalidCastException();
                case FrameType.IVec2:
                case FrameType.IVec3:
                case FrameType.IVec4:
                    return new QVec2(frame._i1, frame._i2);
                case FrameType.Vec2:
                case FrameType.Vec3:
                case FrameType.Vec4:
                    return new QVec2(frame._f1, frame._f2);
            }
        }

        public static explicit operator QColor(in Frame frame)
        {
            if (frame.Type != FrameType.IVec4)
                throw new InvalidCastException();

            return new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
        }

        public static explicit operator QRectangle(in Frame frame)
        {
            switch (frame.Type)
            {
                default:
                    throw new InvalidCastException();
                case FrameType.IVec4:
                    return new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
                case FrameType.Vec4:
                    return new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4);
            } 
        }
        
        public static explicit operator QLine(in Frame frame)
        {
            switch (frame.Type)
            {
                default:
                    throw new InvalidCastException();
                case FrameType.IVec4:
                    return new QLine(frame._i1, frame._i2, frame._i3, frame._i4);
                case FrameType.Vec4:
                    return new QLine(frame._f1, frame._f2, frame._f3, frame._f4);
            }
        }
        #endregion

        public static explicit operator Frame(int i)              => new Frame(i);
        public static explicit operator Frame(float f)            => new Frame(f);
        public static implicit operator Frame(Command cmd)        => new Frame(cmd);
        public static implicit operator Frame(in QVec2 vector)    => new Frame(vector.X, vector.Y);
        public static implicit operator Frame(in QColor color)    => new Frame(color.R, color.G, color.B, color.A);
        public static implicit operator Frame(in QRectangle rect) => new Frame(rect.Max.X, rect.Max.Y, rect.Min.X, rect.Min.Y);
        public static implicit operator Frame(in QLine line)      => new Frame(line.Start.X, line.Start.Y, line.End.X, line.Start.Y);

        public static void Create(in QBezier bezier, out Frame a, out Frame b)
        {
            a = new Frame(bezier.Start.X, bezier.Start.Y, bezier.End.X, bezier.End.Y);
            b = new Frame(bezier.ControlA.X, bezier.ControlA.Y, bezier.ControlB.X, bezier.ControlB.Y);
        }

        public static void Create(in QEllipse ellipse, out Frame a, out Frame b)
        {
            a = new Frame(ellipse.Center.X, ellipse.Center.Y);
            b = new Frame(ellipse.AxisA.X, ellipse.AxisA.Y, ellipse.AxisB.X, ellipse.AxisB.Y);
        }
    }
}