//>>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
//<<< _using
using System.IO;

using Newtonsoft.Json;

namespace Framefield.Core.ID06d920b1_01ae_413c_82f7_98aabc5241eb
{
    public class Class_SoundLevels : OperatorPart.Function
    {
        //>>> _inputids
        private enum InputId
        {
            FilePath = 0,
            Time = 1,
            Threshold = 2,
            Smooth = 3,
            BeatTimeMode = 4,
            FlashDecay = 5
        }
        //<<< _inputids
        
        //>>> _outputids
        private enum OutputId
        {
            Level = 0,
            BeatIndex = 1,
            BeatTime = 2
        }
        //<<< _outputids


        private void SmoothBuffer(ref float[] inBuffer, ref float[] outBuffer, int sampleCount, int stepWidth =1) 
        {
            for(var i = 0; i < inBuffer.Length; i++ ) 
            {
                var average =0f;
                var count = 0f;
                
                for(var ds = 0 ; ds < sampleCount; ds++ ) 
                {
                    var smoothI = i + (-sampleCount / 2 + ds) * stepWidth  ;
                    
                    if(smoothI < 0 || smoothI >= inBuffer.Length)
                        continue;
                        
                    average+= inBuffer[smoothI];
                    count ++;
                }
                outBuffer[i] = average/count;                    
            }
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) {
            //>>> _params
            var FilePath = inputs[(int)InputId.FilePath].Eval(context).Text;
            var Time = inputs[(int)InputId.Time].Eval(context).Value;
            var Threshold = inputs[(int)InputId.Threshold].Eval(context).Value;
            var Smooth = inputs[(int)InputId.Smooth].Eval(context).Value;
            var BeatTimeMode = (int) inputs[(int)InputId.BeatTimeMode].Eval(context).Value;
            var FlashDecay = inputs[(int)InputId.FlashDecay].Eval(context).Value;
            //<<< _params
            
            
            var needsRecalcAverage = false;

            if( FilePath != _filepath) {
                //Logger.Info("here" + FilePath);
                _filepath = FilePath;
                
                if (File.Exists(_filepath))
                {
                    using (var reader = new StreamReader(FilePath))
                    {
                        var jsonString = reader.ReadToEnd();
                        _uplevels = JsonConvert.DeserializeObject<float[]>(jsonString);
                        if (_uplevels == null || _uplevels.Length == 0)
                        {
                            Logger.Warn("Loading sound levels palletes failed");
                            return context;
                        }        
                        Logger.Info(this, "Updated volumes. Samples" + _uplevels.Length);
                    }                    
                }
                else {
                    Logger.Warn(this, "File doesn't exist:  " + _filepath);
                }
                needsRecalcAverage = true;
            }
            
            var smoothWindow = (int)Utilities.Clamp(Smooth, 1, MAX_SMOOTH_WINDOW);
            if(needsRecalcAverage ||  smoothWindow != _smoothWindow) 
            {
                _smoothWindow = smoothWindow;                                
                _maxLevel = 0f;
                _averageLevels = new float[_uplevels.Length];
                _averageLevels2 = new float[_uplevels.Length];
                
                foreach(var l in _uplevels) 
                {
                    _maxLevel = (float)Math.Max(l, _maxLevel);
                }
                
                SmoothBuffer( ref _uplevels, ref _averageLevels2, _smoothWindow, 1);
                SmoothBuffer( ref _averageLevels2, ref _averageLevels, _smoothWindow , 2);
                SmoothBuffer( ref _averageLevels, ref _averageLevels2, _smoothWindow , 4);
                SmoothBuffer( ref _averageLevels2, ref _averageLevels, _smoothWindow , 8);
            }


               
            
            var index = (int)(Time*100.0);
            UpdateBeatBoundaries(index, Threshold);
            context.Value = 0;
            
            
            if(_uplevels != null && index < _uplevels.Length && index > 0 && _maxLevel > 0.001f) 
            {
                switch((OutputId)outputIdx) {
                    case OutputId.Level:
                        context.Value = _averageLevels[index];
                        break;
                        
                    case OutputId.BeatTime:
                        var mode = (OutputType)(BeatTimeMode);
                        switch((OutputType)(BeatTimeMode)) 
                        {                    
                            case OutputType.Peaks:
                                context.Value = (float)Math.Max(0, _uplevels[index] - _averageLevels[index]);                    
                            	break;
                            	
                            case OutputType.Flashes:
                                var t = ((float)index - _beatStartIndex) / (float)SAMPLE_RESOLUTION_PER_SECOND;
                                context.Value =  (float)Math.Pow(2.71f, -FlashDecay* t);
                            	break;
                            	
                            case OutputType.FlashesWithIntensity:
                                var t2 = ((float)index - _beatStartIndex) / (float)SAMPLE_RESOLUTION_PER_SECOND;
                                context.Value =  (float)Math.Pow(2.71f, -FlashDecay* t2) * _uplevels[_beatStartIndex];
                            	break;
                            	
                            case OutputType.TimeSincePeak:
                                context.Value = ((float)index - _beatStartIndex) / (float)SAMPLE_RESOLUTION_PER_SECOND;
                            	break;
                            	
                            case OutputType.TimeToPeak:
                                context.Value = (_beatEndIndex  - (float)index) / (float)SAMPLE_RESOLUTION_PER_SECOND;                    
                            	break;
                        }
                        break;
                    
                    case OutputId.BeatIndex:
                        context.Value = (float)_beatStartIndex;
                    	break;
                }
            }
            
            return context;
        }
        
        private enum OutputType  {
            Peaks = 1,
            Flashes,
            TimeSincePeak,
            TimeToPeak,
            FlashesWithIntensity,
        }
        
        private void UpdateBeatBoundaries(int timeIndex, float threshold) 
        {
            if (timeIndex >= _beatStartIndex && timeIndex <= _beatEndIndex && threshold == _lastThreshold) 
                return;
            
            if(timeIndex < 0 || timeIndex >= _uplevels.Length)
                return;
            
            _lastThreshold = threshold;

            const int MAX_STEPS = 12 * SAMPLE_RESOLUTION_PER_SECOND;
            
            // Find left boundary
            bool wasAboveThreshold = false;
            var maxLevel = 0f;
            var steps = 0;
            for(steps = 0; steps < MAX_STEPS && (timeIndex - steps) >=0; steps++) 
            {                                
                _beatStartIndex = timeIndex - steps;
                var level = _uplevels[_beatStartIndex] - _averageLevels[_beatStartIndex];                
                maxLevel = (float)Math.Max(maxLevel, level);
                var isAboveThreshold = level > threshold;
                var isFlank= (steps > 0 && wasAboveThreshold && !isAboveThreshold);
                
                if(isFlank) {
                    break;
                }                
                wasAboveThreshold = isAboveThreshold;                
            }

            // Find right boundary            
            for(steps = 0; steps < MAX_STEPS && (timeIndex + steps) < _averageLevels.Length; steps++) 
            {                                
                _beatEndIndex = timeIndex + steps;
                var level = _uplevels[_beatEndIndex] - _averageLevels[_beatEndIndex];                
                var isAboveThreshold = level > threshold;
                var isFlank= (steps > 0 && !wasAboveThreshold && isAboveThreshold);
                
                if(isFlank) {
                    break;
                }                
                wasAboveThreshold = isAboveThreshold;                
            }
        }
        
        private const int MAX_SMOOTH_WINDOW = 500;
        
        private const int SAMPLE_RESOLUTION_PER_SECOND = 100;
        string _filepath = ""; 
        string _content ="";        
        private float[] _uplevels = new float[0];
        private int _smoothWindow;
        
        private float[] _averageLevels= new float[0];
        private float[] _averageLevels2 = new float[0];
        private float _maxLevel;
        private int _beatStartIndex;
        private int _beatEndIndex;
        private float _lastThreshold;
        
    }
}

