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

namespace Framefield.Core.ID1dafae7b_cfcd_4fb1_bb13_6aa71effe76b
{
    public class Class_BeatTapper : OperatorPart.Function, Framefield.Core.OperatorPartTraits.ITimeAccessor
    {
        //>>> _inputids
        private enum InputId
        {
            TapTrigger = 0,
            ResyncTrigger = 1,
            DefaultBPM = 2,
            TriggerImpact = 3,
            SmoothSyncChanges = 4,
            BeatsPerBar = 5,
            ResetTotalTrigger = 6,
            SmoothBPMChanges = 7
        }
        //<<< _inputids
        
        //>>> _outputids
        private enum OutputId
        {
            Fragment = 0,
            BPM = 1,
            SyncedTime = 2
        }
        //<<< _outputids
                
        /*
            This code seems much too complicated, but getting flexible and conherent beat detection
            seems to be much trickier, than I though. After playing with a couple of methods, it 
            finally settled on keeping a "fragmentTime" counter wrapping over the _beatDuration.
            The fragmentTime is than additionaly offset with. Maybe there is a method that works
            without the separate offset-variable, but since I wanted to have damped transition is
            syncing (no jumps please), keeping both seperated seemed to work.
        */
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx) 
        {
            //>>> _params
            var TapTrigger = inputs[(int)InputId.TapTrigger].Eval(context).Value;
            var ResyncTrigger = inputs[(int)InputId.ResyncTrigger].Eval(context).Value;
            var DefaultBPM = inputs[(int)InputId.DefaultBPM].Eval(context).Value;
            var TriggerImpact = inputs[(int)InputId.TriggerImpact].Eval(context).Value;
            var SmoothSyncChanges = inputs[(int)InputId.SmoothSyncChanges].Eval(context).Value;
            var BeatsPerBar = inputs[(int)InputId.BeatsPerBar].Eval(context).Value;
            var ResetTotalTrigger = inputs[(int)InputId.ResetTotalTrigger].Eval(context).Value;
            var SmoothBPMChanges = inputs[(int)InputId.SmoothBPMChanges].Eval(context).Value;
            //<<< _params
            
            if( _dampedBeatDuration == 0 || DefaultBPM !=_lastDefaultBpm)  {
                _lastDefaultBpm = DefaultBPM;
                _dampedBeatDuration = 60 / DefaultBPM;
            }
            
           

            var timeDelta = context.GlobalTime - _lastFragmentUpdateTime;

            if(timeDelta != 0) 
            {            
                _lastFragmentUpdateTime = context.GlobalTime;
                            
                _fragmentTime += timeDelta;
                var barDuration = _dampedBeatDuration * BeatsPerBar;
                
                _fragmentTime %= barDuration;
                //if( _fragmentTime > barDuration)
                //    _fragmentTime -= barDuration;
                            
                // Detect Flanks
                bool justTapped = (TapTrigger - _oldTapTrigger > 0.5f);
                _oldTapTrigger =TapTrigger;
    
                bool justResynced = (ResyncTrigger - _oldResyncTrigger > 0.5f);
                _oldResyncTrigger =ResyncTrigger;
                
                if(ResetTotalTrigger > 0.5f) {
                    _barCounter = 0;
                }
                
                
                if(justTapped) 
                {                
                    bool newSeriesStarted = _tapTimes.Count == 0 || Math.Abs(context.GlobalTime - _tapTimes.Last()) > 4* _beatDuration;
                    
                    if(newSeriesStarted) {
                        Logger.Info("New Tap Series started");
                        _tapTimes.Clear();
                    }
    
                    _tapTimes.Add( context.GlobalTime );
                    
                    var zonesCount = _tapTimes.Count - 1; 
                    if( zonesCount > 1) {
                        
                        var lastDuration = _tapTimes[_tapTimes.Count-1] - _tapTimes[_tapTimes.Count-2];
                                            
                        _beatDuration = _beatDuration * (1-TriggerImpact) + lastDuration * TriggerImpact;                        
                        var stepOffset = ( (_fragmentTime - _dampedBeatOffset + _dampedBeatDuration/2) % _dampedBeatDuration ) - _dampedBeatDuration /2 ;
                        
                        _offset += stepOffset * TriggerImpact * 2;
                        //Logger.Info(this, "stepOffset {0:0.00}  FragmentTime {1:0.00} Offset {2:0.00}   BeatDuration {3:0.00} ", stepOffset, _fragmentTime, _offset, _dampedBeatDuration);
                    }
                }

                // Smooth offset and beatduration to avoid jumps
                _dampedBeatDuration = _dampedBeatDuration * SmoothBPMChanges + _beatDuration * (1-SmoothBPMChanges);
                _dampedBeatOffset = _dampedBeatOffset * SmoothSyncChanges + _offset * (1-SmoothSyncChanges);
                
                if(justResynced) {                    
                    _offset = _fragmentTime;
                    if( _offset > barDuration / 2)
                        _offset -= barDuration;
                    
                }
                
                var lastBarTime = _barTime;
                
                _barTime = (_fragmentTime - _dampedBeatOffset) % barDuration;
                if(_barTime < 0)
                    _barTime += barDuration;
                _barTime /= barDuration;
                
                var nextBarStarted =  _barTime - lastBarTime < -barDuration/16 ;
                if (nextBarStarted) {
                    _barCounter++;
                }
            }            
                       
            switch(outputIdx) 
            {
                case (int)OutputId.Fragment:                                 
                    context.Value = 1-(float)_barTime;                    
                    break;
                    
                case (int)OutputId.BPM:
                    context.Value =  (float)(60.0f/_dampedBeatDuration);
                    break;
                    
                case (int)OutputId.SyncedTime:
                    context.Value = (float)(_barCounter + _barTime);
                    break;
                    
//                case (int)OutputId.Offset:
//                    context.Value = (float)(_offset);
//                    break;
            }            
            
            return context;
        }
        
        //double _bpm =0;
        double _offset;
        double _barTime;
        double _dampedBeatDuration= 0.5;
        double _dampedBeatOffset = 0;
        
        int _barCounter = 0;
        
        double _fragmentTime;
        double _lastFragmentUpdateTime;
        
        float _lastDefaultBpm;
        float _oldTapTrigger;
        float _oldResyncTrigger;
        double _beatDuration=1;
        double _syncedTime = 0;
        
        List<double> _tapTimes = new List<double>();
        
    }
}

