//==============================================================
//Adapted by: Philip A Covington, N8VB
////This software is licensed under the GNU General Public License
//==============================================================
//powerspectrum.cs
//power spectrum based on DttSP's power_spectrum.c
//
//==============================================================

using System;
using System.Runtime.InteropServices;

namespace SharpDSP
{    
    public struct PowerSpectrum
    {
        public static event SharpDSPEventHandler sharp_dsp_event;
        
        #region Enumerations

        public enum PowerSpectrumMode : int
        {
            SPECTRUM,
            PANADAPTER,
            SCOPE,
            PHASE,
            PHASE2,
            WATERFALL,
            HISTOGRAM,
            OFF,
            SPECMODE
        }

        public enum Position : int
        {
            PREFILTER,
            POSTFILTER,           
            POSTAGC
        }

        #endregion

        #region Private members

        private Position position;

        private float[] window;
        private float[] ps_results;     //holds power spectrum results
        private float[] scope_results;  //holds scope results
        private float[] phase_results;  //holds phase results

        private FFT fft;
        private FilterDesigner filt_design;

        #endregion

        #region Properties

        private PowerSpectrumMode mode;
        public PowerSpectrumMode Mode
        {
            get { return mode; }
            set
            {
                mode = value;
            }
        }

        private int fft_size;
        public int FFTSize
        {
            get { return fft_size; }
        }

        private int results_size;
        public int ResultsBufferSize
        {
            get { return results_size; }
        }

        #endregion

        #region Constructor

        unsafe public PowerSpectrum(int fft_size, int results_size)
        {
            this.mode = PowerSpectrumMode.OFF;
            this.position = Position.PREFILTER;
            this.fft_size = fft_size;
            this.results_size = results_size;
            this.window = new float[fft_size];
            this.ps_results = new float[results_size]; //don't overrun results buffer
            this.scope_results = new float[results_size];
            this.phase_results = new float[results_size * 2];
            this.fft = new FFT(fft_size);
            this.filt_design = new FilterDesigner();
            fixed (float* ptr_window = &window[0])
            {
                filt_design.makewindow(FilterDesigner.WindowType.BLACKMANHARRIS_WINDOW, fft_size, ptr_window);
            }
        }

        #endregion

        #region Public Methods

        unsafe public void DoPowerSpectrum(float* real, float* imag, Position positon)
        {
            if (mode == PowerSpectrumMode.OFF) { return; }

            switch (positon)
            {   
                case Position.PREFILTER:
                    
                    switch (mode)
                    {
                        case PowerSpectrumMode.PANADAPTER:
                        case PowerSpectrumMode.SPECMODE:
                            this.PowerSpectrumSignal(real, imag);
                            break;
                        case PowerSpectrumMode.PHASE2:
                            this.PowerSpectrumPhase(real, imag);
                            break;
                    }
                    break;
                case Position.POSTFILTER:
                    switch (mode)
                    {
                        case PowerSpectrumMode.HISTOGRAM:
                        case PowerSpectrumMode.WATERFALL:
                        case PowerSpectrumMode.SPECTRUM:
                            this.PowerSpectrumSignal(real, imag);
                            break;
                        case PowerSpectrumMode.SCOPE:
                            this.PowerSpectrumScope(real);
                            break;
                    }
                    break;
                case Position.POSTAGC:
                    switch (mode)
                    {
                        case PowerSpectrumMode.PHASE:
                            this.PowerSpectrumPhase(real, imag);
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
        
        #endregion

        #region Private Methods

        unsafe private void PowerSpectrumSignal(float* real, float* imag)
        {
            float* buffer_real = stackalloc float[this.fft_size];
            float* buffer_imag = stackalloc float[this.fft_size];
            float* sqrMag = stackalloc float[this.fft_size];

            // window the data
            for (int i = 0; i < this.fft_size; i++)
            {
                buffer_real[i] = real[i] * this.window[i];
                buffer_imag[i] = imag[i] * this.window[i];
            }

            this.fft.DoComplexDFTForward(buffer_real, buffer_imag);

            int tmp_size = this.fft_size >> 1;
            for (int i = 0; i < tmp_size; i++)
            {
                sqrMag[i + tmp_size] = buffer_real[i + tmp_size] * buffer_real[i + tmp_size]
                    + buffer_imag[i + tmp_size] * buffer_imag[i + tmp_size];

                this.ps_results[i] =
                    (float)(10.0 * Math.Log10(sqrMag[i + tmp_size] + 1e-180));
                
                sqrMag[i] = buffer_real[i] * buffer_real[i]
                    + buffer_imag[i] * buffer_imag[i];

                this.ps_results[i + tmp_size] =
                    (float)(10.0 * Math.Log10(sqrMag[i] + 1e-180));
            }

            if (sharp_dsp_event != null)
            {
                fixed (float* ptr_buffer = &this.ps_results[0])
                {
                    sharp_dsp_event(this, new SharpDSPEvent(ptr_buffer)); //raise event to indicate power spectrum complete
                }
            }

        }

        unsafe private void PowerSpectrumScope(float* real)
        {
            for (int i = 0; i < this.fft_size; i++)
                this.scope_results[i] = real[i];

            if (sharp_dsp_event != null)
            {
                fixed(float* ptr_buffer = &this.scope_results[0])
                {
                    sharp_dsp_event(this, new SharpDSPEvent(ptr_buffer)); //raise event to indicate power spectrum scope complete
                }
            }
        }

        unsafe private void PowerSpectrumPhase(float* real, float* imag)
        {
            for (int i = 0, j = 0; i < this.fft_size; i++, j += 2)
            {
                this.phase_results[j] = real[i];
                this.phase_results[j + 1] = imag[i];
            }

            if (sharp_dsp_event != null)
            {
                fixed (float* ptr_buffer = &this.phase_results[0])
                {
                    sharp_dsp_event(this, new SharpDSPEvent(ptr_buffer)); //raise event to indicate power spectrum phase complete
                }
            }

        }

        #endregion

    }

}