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
        };

        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;
        }

        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();
            }
        }

        public static explicit operator int(in Frame frame)   => frame._i1;
        public static explicit operator float(in Frame frame) => frame._f1;
        public static explicit operator Command(in Frame frame) => (Command)frame._i1;
        public static explicit operator QVec2(in Frame frame) =>
            frame.IsFloat ? new QVec2(frame._f1, frame._f2) : new QVec2(frame._i1, frame._i2);
        public static explicit operator QColor(in Frame frame) =>
            new QColor((byte)frame._i1, (byte)frame._i2, (byte)frame._i3, (byte)frame._i4);
        public static explicit operator QRectangle(in Frame frame) =>
            frame.IsFloat ?
                new QRectangle(frame._f1, frame._f2, frame._f3, frame._f4) :
                new QRectangle(frame._i1, frame._i2, frame._i3, frame._i4);
        public static explicit operator QLine(in Frame frame) =>
            frame.IsFloat ?
                new QLine(frame._f1, frame._f2, frame._f3, frame._f4) :
                new QLine(frame._i1, frame._i2, frame._i3, frame._i4);

        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);
        }
    }
}