using System;
using System.Security;
using System.Runtime.InteropServices;
using InterlMKLLib.mkl;

namespace InterlMKLLib
{
    public class IntelMKL
    {
        private static IntelMKL _instance;

        private static MKL _mkl16384;
        private static MKL _mkl8192;
        private static MKL _mkl4096;
        private static MKL _mkl2048;
        private static MKL _mkl1024;
        private static MKL _mkl512;
        private static MKL _mkl256;
        private static MKL _mkl128;
        private static MKL _mkl64;
        private static MKL _mkl32;
        private static MKL _mkl16;
        private static MKL _mkl8;
        private static MKL _mkl4;
        private static MKL _mkl2;

        static IntelMKL()
        {
            _instance = new IntelMKL();
        }

        public static IntelMKL Instance { get { return _instance; } }

        private static object _mklLock = new object();

        public double[] FFT(double[] vector)
        {
            int FFTNumber = vector.Length;

            switch (FFTNumber)
            {
                case (2):
                    if (_mkl2 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl2 == null)
                            {
                                _mkl2 = new MKL(2);
                            }
                        }
                    }
                    return _mkl2.FFT(vector);
                case (4):
                    if (_mkl4 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl4 == null)
                            {
                                _mkl4 = new MKL(4);
                            }
                        }
                    }
                    return _mkl4.FFT(vector);
                case (8):
                    if (_mkl8 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl8 == null)
                            {
                                _mkl8 = new MKL(8);
                            }
                        }
                    }
                    return _mkl8.FFT(vector);
                case (16):
                    if (_mkl16 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl16 == null)
                            {
                                _mkl16 = new MKL(16);
                            }
                        }
                    }
                    return _mkl16.FFT(vector);
                case (32):
                    if (_mkl32 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl32 == null)
                            {
                                _mkl32 = new MKL(32);
                            }
                        }
                    }
                    return _mkl32.FFT(vector);
                case (64):
                    if (_mkl64 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl64 == null)
                            {
                                _mkl64 = new MKL(64);
                            }
                        }
                    }
                    return _mkl64.FFT(vector);
                case (128):
                    if (_mkl128 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl128 == null)
                            {
                                _mkl128 = new MKL(128);
                            }
                        }
                    }
                    return _mkl128.FFT(vector);
                case (256):
                    if (_mkl256 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl256 == null)
                            {
                                _mkl256 = new MKL(256);
                            }
                        }
                    }
                    return _mkl256.FFT(vector);
                case (512):
                    if (_mkl512 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl512 == null)
                            {
                                _mkl512 = new MKL(512);
                            }
                        }
                    }
                    return _mkl512.FFT(vector);
                case (1024):
                    if (_mkl1024 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl1024 == null)
                            {
                                _mkl1024 = new MKL(1024);
                            }
                        }
                    }
                    return _mkl1024.FFT(vector);
                case (2048):
                    if (_mkl2048 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl2048 == null)
                            {
                                _mkl2048 = new MKL(2048);
                            }
                        }
                    }
                    return _mkl2048.FFT(vector);
                case (4096):
                    if (_mkl4096 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl4096 == null)
                            {
                                _mkl4096 = new MKL(4096);
                            }
                        }
                    }
                    return _mkl4096.FFT(vector);
                case (8192):
                    if (_mkl8192 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl8192 == null)
                            {
                                _mkl8192 = new MKL(8192);
                            }
                        }
                    }
                    return _mkl8192.FFT(vector);
                case (16384):
                    if (_mkl16384 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl16384 == null)
                            {
                                _mkl16384 = new MKL(16384);
                            }
                        }
                    }
                    return _mkl16384.FFT(vector);
                default:
                    throw new ArgumentException("FFT " + FFTNumber + " not supported");
            }

        }

        public double[] IFFT(double[] vector)
        {
            int IFFTNumber = vector.Length;

            switch (IFFTNumber)
            {
                case (2):
                    if (_mkl2 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl2 == null)
                            {
                                _mkl2 = new MKL(2);
                            }
                        }
                    }
                    return _mkl2.IFFT(vector);
                case (4):
                    if (_mkl4 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl4 == null)
                            {
                                _mkl4 = new MKL(4);
                            }
                        }
                    }
                    return _mkl4.IFFT(vector);
                case (8):
                    if (_mkl8 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl8 == null)
                            {
                                _mkl8 = new MKL(8);
                            }
                        }
                    }
                    return _mkl8.IFFT(vector);
                case (16):
                    if (_mkl16 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl16 == null)
                            {
                                _mkl16 = new MKL(16);
                            }
                        }
                    }
                    return _mkl16.IFFT(vector);
                case (32):
                    if (_mkl32 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl32 == null)
                            {
                                _mkl32 = new MKL(32);
                            }
                        }
                    }
                    return _mkl32.IFFT(vector);
                case (64):
                    if (_mkl64 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl64 == null)
                            {
                                _mkl64 = new MKL(64);
                            }
                        }
                    }
                    return _mkl64.IFFT(vector);
                case (128):
                    if (_mkl128 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl128 == null)
                            {
                                _mkl128 = new MKL(128);
                            }
                        }
                    }
                    return _mkl128.IFFT(vector);
                case (256):
                    if (_mkl256 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl256 == null)
                            {
                                _mkl256 = new MKL(256);
                            }
                        }
                    }
                    return _mkl256.IFFT(vector);
                case (512):
                    if (_mkl512 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl512 == null)
                            {
                                _mkl512 = new MKL(512);
                            }
                        }
                    }
                    return _mkl512.IFFT(vector);
                case (1024):
                    if (_mkl1024 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl1024 == null)
                            {
                                _mkl1024 = new MKL(1024);
                            }
                        }
                    }
                    return _mkl1024.IFFT(vector);
                case (2048):
                    if (_mkl2048 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl2048 == null)
                            {
                                _mkl2048 = new MKL(2048);
                            }
                        }
                    }
                    return _mkl2048.IFFT(vector);
                case (4096):
                    if (_mkl4096 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl4096 == null)
                            {
                                _mkl4096 = new MKL(4096);
                            }
                        }
                    }
                    return _mkl4096.IFFT(vector);
                case (8192):
                    if (_mkl8192 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl8192 == null)
                            {
                                _mkl8192 = new MKL(8192);
                            }
                        }
                    }
                    return _mkl8192.IFFT(vector);
                case (16384):
                    if (_mkl16384 == null)
                    {
                        lock (_mklLock)
                        {
                            if (_mkl16384 == null)
                            {
                                _mkl16384 = new MKL(16384);
                            }
                        }
                    }
                    return _mkl16384.IFFT(vector);
                default:
                    throw new ArgumentException("IFFT " + IFFTNumber + " not supported");
            }
        }
    }

    public class MKL
    {      
        private IntPtr Desc;

        public MKL(int length)
        {
            int precision = DFTI.DOUBLE;
            int forward_domain = DFTI.REAL;
            int dimension = 1;
         
            /* Create new DFTI descriptor */
            int ret = DFTI.DftiCreateDescriptor(ref Desc, precision, forward_domain, dimension, length);

            /* Setup the scale factor */
            long transform_size = length;
            double scale_factor = 1.0 / transform_size;
            DFTI.DftiSetValue(Desc, DFTI.BACKWARD_SCALE, scale_factor);

            /* Try floating-point and GetValue function */
            double backward_scale = 0;
            DFTI.DftiGetValue(Desc, DFTI.BACKWARD_SCALE, ref backward_scale);

            /* Setup the transform parameters */
            DFTI.DftiSetValue(Desc, DFTI.PLACEMENT, DFTI.NOT_INPLACE);
            DFTI.DftiSetValue(Desc, DFTI.PACKED_FORMAT, DFTI.PACK_FORMAT);

            /* Commit the descriptor */
            DFTI.DftiCommitDescriptor(Desc);
        }

        public double[] FFT(double[] vector)
        {
            /* Forward, then backward transform */
            DFTI.DftiComputeForward(Desc, vector, vector);
            //double[] x_transformed_2 = new double[OfCyclesInWaveform];
            //DFTI.DftiComputeBackward(desc, x_transformed, x_transformed_2);

            //DFTI.DftiFreeDescriptor(ref desc);
            return vector;
        }

        public double[] IFFT(double[] vector)
        {
            /* Forward, then backward transform */
            DFTI.DftiComputeBackward(Desc, vector, vector);
            //double[] x_transformed_2 = new double[OfCyclesInWaveform];
            //DFTI.DftiComputeBackward(desc, x_transformed, x_transformed_2);

            //DFTI.DftiFreeDescriptor(ref desc);
            return vector;
        }
    }

    namespace mkl
    {
        public sealed class DFTI
        {
            private DFTI() { }

            /** Constants for DFTI, file "mkl_dfti.h" */
            /** DFTI configuration parameters */
            public static int PRECISION = 3;
            public static int FORWARD_DOMAIN = 0;
            public static int DIMENSION = 1;
            public static int LENGTHS = 2;
            public static int NUMBER_OF_TRANSFORMS = 7;
            public static int FORWARD_SCALE = 4;
            public static int BACKWARD_SCALE = 5;
            public static int PLACEMENT = 11;
            public static int COMPLEX_STORAGE = 8;
            public static int REAL_STORAGE = 9;
            public static int CONJUGATE_EVEN_STORAGE = 10;
            public static int DESCRIPTOR_NAME = 20;
            public static int PACKED_FORMAT = 21;
            public static int NUMBER_OF_USER_THREADS = 26;
            public static int INPUT_DISTANCE = 14;
            public static int OUTPUT_DISTANCE = 15;
            public static int INPUT_STRIDES = 12;
            public static int OUTPUT_STRIDES = 13;
            public static int ORDERING = 18;
            public static int TRANSPOSE = 19;
            public static int COMMIT_STATUS = 22;
            public static int VERSION = 23;
            /** DFTI configuration values */
            public static int SINGLE = 35;
            public static int DOUBLE = 36;
            public static int COMPLEX = 32;
            public static int REAL = 33;
            public static int INPLACE = 43;
            public static int NOT_INPLACE = 44;
            public static int COMPLEX_COMPLEX = 39;
            public static int REAL_REAL = 42;
            public static int COMPLEX_REAL = 40;
            public static int REAL_COMPLEX = 41;
            public static int COMMITTED = 30;
            public static int UNCOMMITTED = 31;
            public static int ORDERED = 48;
            public static int BACKWARD_SCRAMBLED = 49;
            public static int NONE = 53;
            public static int CCS_FORMAT = 54;
            public static int PACK_FORMAT = 55;
            public static int PERM_FORMAT = 56;
            public static int CCE_FORMAT = 57;
            public static int VERSION_LENGTH = 198;
            public static int MAX_NAME_LENGTH = 10;
            public static int MAX_MESSAGE_LENGTH = 40;
            /** DFTI predefined error classes */
            public static int NO_ERROR = 0;
            public static int MEMORY_ERROR = 1;
            public static int INVALID_CONFIGURATION = 2;
            public static int INCONSISTENT_CONFIGURATION = 3;
            public static int NUMBER_OF_THREADS_ERROR = 8;
            public static int MULTITHREADED_ERROR = 4;
            public static int BAD_DESCRIPTOR = 5;
            public static int UNIMPLEMENTED = 6;
            public static int MKL_INTERNAL_ERROR = 7;
            public static int LENGTH_EXCEEDS_INT32 = 9;

            /** DFTI DftiCreateDescriptor wrapper */
            public static int DftiCreateDescriptor(ref IntPtr desc,
                int precision, int domain, int dimention, int length)
            {
                return DFTINative.DftiCreateDescriptor(ref desc,
                    precision, domain, dimention, length);
            }
            /** DFTI DftiFreeDescriptor wrapper */
            public static int DftiFreeDescriptor(ref IntPtr desc)
            {
                return DFTINative.DftiFreeDescriptor(ref desc);
            }
            /** DFTI DftiSetValue wrapper */
            public static int DftiSetValue(IntPtr desc,
                int config_param, int config_val)
            {
                return DFTINative.DftiSetValue(desc,
                    config_param, config_val);
            }
            /** DFTI DftiSetValue wrapper */
            public static int DftiSetValue(IntPtr desc,
                int config_param, double config_val)
            {
                return DFTINative.DftiSetValue(desc,
                    config_param, config_val);
            }
            /** DFTI DftiGetValue wrapper */
            public static int DftiGetValue(IntPtr desc,
                int config_param, ref double config_val)
            {
                return DFTINative.DftiGetValue(desc,
                    config_param, ref config_val);
            }
            /** DFTI DftiCommitDescriptor wrapper */
            public static int DftiCommitDescriptor(IntPtr desc)
            {
                return DFTINative.DftiCommitDescriptor(desc);
            }
            /** DFTI DftiComputeForward wrapper */
            public static int DftiComputeForward(IntPtr desc,
                [In] double[] x_in, [Out] double[] x_out)
            {
                return DFTINative.DftiComputeForward(desc, x_in, x_out);
            }
            /** DFTI DftiComputeBackward wrapper */
            public static int DftiComputeBackward(IntPtr desc,
                [In] double[] x_in, [Out] double[] x_out)
            {
                return DFTINative.DftiComputeBackward(desc, x_in, x_out);
            }
        }

        /** DFTI native declarations */
        [SuppressUnmanagedCodeSecurity]
        internal sealed class DFTINative
        {
            /** DFTI native DftiCreateDescriptor declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiCreateDescriptor(ref IntPtr desc,
                int precision, int domain, int dimention, int length);
            /** DFTI native DftiCommitDescriptor declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiCommitDescriptor(IntPtr desc);
            /** DFTI native DftiFreeDescriptor declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiFreeDescriptor(ref IntPtr desc);
            /** DFTI native DftiSetValue declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiSetValue(IntPtr desc,
                int config_param, int config_val);
            /** DFTI native DftiSetValue declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiSetValue(IntPtr desc,
                int config_param, double config_val);
            /** DFTI native DftiGetValue declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiGetValue(IntPtr desc,
                int config_param, ref double config_val);
            /** DFTI native DftiComputeForward declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiComputeForward(IntPtr desc,
                [In] double[] x_in, [Out] double[] x_out);
            /** DFTI native DftiComputeBackward declaration */
            [DllImport("mkl_rt.dll", CallingConvention = CallingConvention.Cdecl,
                 ExactSpelling = true, SetLastError = false)]
            internal static extern int DftiComputeBackward(IntPtr desc,
                [In] double[] x_in, [Out] double[] x_out);
        }
    }
}
