using UnityEngine;

public class WaveData : Singleton<WaveData>
{
    public class WavePropertiesChannel
    {
        public WaveProperties[] channel;

        public WavePropertiesChannel(WaveProperties[] channel_)
        {
            channel = channel_;
        }
    }

    public class WaveProperties
    {
        public float amplitude;
        public float bass;
        public float treble;

        public WaveProperties(float amplitude_, float bass_, float treble_)
        {
            amplitude = amplitude_;
            bass = bass_;
            treble = treble_;
        }
    }

    [Header("Song")]
    public AudioSource sourceSong;

    [Header("Properties")]
    public float resampleTime = 0.1f;

    public float bufferDelay = 10;
    public float distanceBetweenSamples = 0.1f;
    public int bufferSize = 300;
    public int outputSampleNum = 1024;
    public int spectrumSampleNum = 1024;

    [Header("Sections")]
    public int maxBass = 25;

    public int minTreble = 220;

    [Header("Limits")]
    public float trebleToThunderValue = 0.5f;

    public float bassToExplodeValue = 0.5f;
    public float songOffsetPercentage = 0.25f;

    [Header("Debug")]
    public bool debug = false;

    public WavePropertiesChannel[] bufferChannels;

    private float time_passed_for_resampling;

    private int index;

    private AudioSource sounding_song;

    private float _freq_sample;

    public float charPos
    {
        get
        {
            int buffer_index_pos = Mathf.RoundToInt(bufferSize * songOffsetPercentage);
            return buffer_index_pos * distanceBetweenSamples;
        }
    }

    public int charIndex
    {
        get
        {
            int buffer_index_pos = Mathf.RoundToInt(bufferSize * songOffsetPercentage);
            return buffer_index_pos;
        }
    }

    private void Awake()
    {
        sourceSong.Play();

        bufferChannels = new WavePropertiesChannel[sourceSong.clip.channels];
        for (int i = 0; i < sourceSong.clip.channels; i++)
        {
            WaveProperties[] wp_array = new WaveProperties[bufferSize];

            for (int j = 0; j < bufferSize; j++)
            {
                wp_array[j] = new WaveProperties(0f, 0f, 0f);
            }

            bufferChannels[i] = new WavePropertiesChannel(wp_array);
        }

        GameObject sound_object = new GameObject("MainSong");
        sound_object.transform.SetParent(sourceSong.transform);
        sounding_song = sound_object.AddComponent<AudioSource>();
        sounding_song.clip = sourceSong.clip;
        sounding_song.PlayDelayed(bufferDelay);

        _freq_sample = AudioSettings.outputSampleRate;
    }

    private void FixedUpdate()
    {
        time_passed_for_resampling += Time.deltaTime;

        if (time_passed_for_resampling >= resampleTime)
        {
            time_passed_for_resampling = 0f;

            float[] spectrum = new float[spectrumSampleNum];
            sourceSong.GetSpectrumData(spectrum, 0, FFTWindow.BlackmanHarris);

            float[] output_samples = new float[outputSampleNum];
            sourceSong.GetOutputData(output_samples, 0);

            float amplitude = _GetAmplitudeOfCurrent(output_samples);
            float bass = _GetBassOfCurrent(spectrum);
            float treble = _GetTrebleOfCurrent(spectrum);

            for (int i = 0; i < sourceSong.clip.channels; i++)
            {
                _AddNewStep(i, new WaveProperties(amplitude, bass, treble));
            }
        }
        else
        {
            for (int i = 0; i < sourceSong.clip.channels; i++)
            {
                _AddEmptyStep(i);
            }
        }

        WaveProperties[] wp = bufferChannels[0].channel;

        if (debug)
        {
            //Debug
            float last_pos = 0;
            for (int i = 0; i < bufferSize - 1; i++)
            {
                Debug.DrawLine(new Vector2(last_pos, -10 + wp[i].bass * 10), new Vector2(last_pos + 1, -10 + wp[i + 1].bass * 10), Color.black);
                Debug.DrawLine(new Vector2(last_pos, wp[i].amplitude * 10), new Vector2(last_pos + 1, wp[i + 1].amplitude * 10), Color.green);
                Debug.DrawLine(new Vector2(last_pos, 10 + wp[i].treble * 10), new Vector2(last_pos + 1, 10 + wp[i + 1].treble * 10), Color.blue);
                last_pos += 1;
            }

            Debug.DrawLine(new Vector2(charPos, -5f), new Vector2(charPos, 5f), Color.cyan);
        }
    }

    private void _AddEmptyStep(int channel_)
    {
        _AddNewStep(channel_, GetLastOfChannel(channel_));
    }

    private void _AddNewStep(int channel_, WaveProperties props_)
    {
        SetLastOfChannel(channel_, props_);
    }

    private float _GetAmplitudeOfCurrent(float[] samples_)
    {
        return _GetAmplitudeOfSection(samples_, 0, outputSampleNum);
    }

    private float _GetBassOfCurrent(float[] spectrum_)
    {
        return _GetAmplitudeOfSection(spectrum_, 0, maxBass);
    }

    private float _GetTrebleOfCurrent(float[] spectrum_)
    {
        return _GetAmplitudeOfSection(spectrum_, minTreble, spectrumSampleNum);
    }

    private float _GetAmplitudeOfSection(float[] samples_, int min_, int max_)
    {
        float max_sample = 0;
        for (int i = min_; i < max_; i++)
        {
            if (samples_[i] > max_sample)
            {
                max_sample = samples_[i];
            }
        }

        return max_sample;
    }

    private void SetLastOfChannel(int channel, WaveProperties value_)
    {
        WaveProperties[] wp = bufferChannels[channel].channel;

        for (int i = 0; i < bufferSize - 1; i++)
        {
            wp[i] = wp[i + 1];
        }
        wp[bufferSize - 1] = value_;
    }

    public WaveProperties GetLastOfChannel(int channel_)
    {
        return bufferChannels[channel_].channel[bufferSize - 1];
    }

    public WaveProperties[] GetChanel(int channel_)
    {
        return bufferChannels[channel_].channel;
    }
}