using System;
using System.Collections.Generic;
using System.Linq;
using SharpDX;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using Framefield.Core.Rendering;
using Buffer = SharpDX.Direct3D11.Buffer;

using NAudio.Midi;
using System.IO;

namespace Framefield.Core.ID47d6c84b_7429_4dc4_9dd4_67147a99a66c
{
    public class Class_ReplicateWithMidiNote : FXSourceCodeFunction
    {
        #region Renderer
        public class Renderer : BaseRenderer
        {
            public override void SetupEffect(OperatorPartContext context)
            {
                base.SetupEffect(context);
                try
                {
                    SetupMaterialConstBuffer(context);
                    SetupFogSettingsConstBuffer(context);
                    SetupPointLightsConstBuffer(context);
                }
                catch (Exception e)
                {
                    Logger.Error(ParentFunc, "Error building constant buffer: {0} - Source: {1}", e.Message, e.Source);
                }
            }
            public OperatorPart.Function ParentFunc {get;set;}            
        }
        #endregion

        public Class_ReplicateWithMidiNote()
        {
            _renderer = new Renderer(){ParentFunc = this};
        }

        public override void Dispose()
        {
            Utilities.DisposeObj(ref _renderer);
            base.Dispose();
        }

        //>>> _inputids
        private enum InputId
        {
            Code = 0,
            SceneInput = 1,
            ScaleY = 2,
            FilePath = 3,
            TimeRate = 4,
            TimeOffset = 5
        }
        //<<< _inputids


        struct MidiNote
        {
            public double StartTime;
            public double Duration;
            public int NoteNumber;
            public int Channel;
            public int Velocity;
            public int TrackId;
        }

        private NAudio.Midi.MidiFile _midiFile;
        private double _lastTime;
        private int _track;
        private int _trackCount;
        private bool _firstEval = true;
        private string _filePath;
        
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
        
            //>>> _params
            var Code = inputs[(int)InputId.Code].Eval(context).Text;
            var SceneInput = inputs[(int)InputId.SceneInput];
            var ScaleY = inputs[(int)InputId.ScaleY].Eval(context).Value;
            var FilePath = inputs[(int)InputId.FilePath].Eval(context).Text;
            var TimeRate = inputs[(int)InputId.TimeRate].Eval(context).Value;
            var TimeOffset = inputs[(int)InputId.TimeOffset].Eval(context).Value;
            //<<< _params        

            if (SceneInput.Connections.Count == 0)
            {
                return context;
            }

            if (_firstEval)
            {
                for (int i = 0; i < NumCodes(); ++i)
                    Compile(i);
                _firstEval = false;
                Changed = true;
            }

            const float toRad = (float) Math.PI/180f;

            var prevTransform = context.ObjectTWorld;

            //var meshCollector = new OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier>();
            _meshCollector.Clear();
            SceneInput.TraverseWithFunction(_meshCollector, null);
            var meshSupplier = _meshCollector.CollectedOpPartFunctions.FirstOrDefault();
            if (meshSupplier == null)
            {
                Logger.Error(this,"Found no mesh supplier, have you forgotten to add an input?");
                return context;
            }
            var meshes = new List<Mesh>();
            meshSupplier.AddMeshesTo(meshes);
            if (meshes.Count != 1)
            {
                Logger.Error(this,"Found more or less than 1 mesh");
                return context;
            }
            var instancedMesh = meshes[0];

            // instance data buffer
            const int instanceDataSize = 4*16; // float4x4
            var numInstances = 100;
            
            // Load midifile
            //if( FilePath == _filePath)
            //    return context;
            
            _filePath = FilePath;

            if (!File.Exists(_filePath))
            {
                Logger.Info("Can't find midifile {0}", _filePath);
                return context;
            }

            _midiFile = new MidiFile(_filePath);

            _trackCount = _midiFile.Tracks;


            var playedNoteEvents = new List<NoteOnEvent>();

            var notes = new List<MidiNote>();

            double ppqn = TimeRate;

            if ( _midiFile != null)
            {
                //Logger.Info("Structure of MidiFile...");
                for (var trackIndex = 0; trackIndex < _trackCount; ++trackIndex)
                {
                    var events = _midiFile.Events.GetTrackEvents(trackIndex);
                    foreach (var e in events)
                    {
                        if (e.CommandCode == MidiCommandCode.NoteOn) {
                            var noteOn = e as NoteOnEvent;
                            notes.Add(new MidiNote()
                            {
                                TrackId = trackIndex,
                                StartTime = (double)e.AbsoluteTime / ppqn,
                                Channel = e.Channel,
                                NoteNumber = noteOn.NoteNumber,
                                Duration = (double)noteOn.NoteLength,
                                Velocity = noteOn.Velocity
                            });

                            //playedNoteEvents.Add(noteOn);
                        }
                        else if (e.CommandCode == MidiCommandCode.NoteOff) {
                        }
                        else if(e.CommandCode == MidiCommandCode.ControlChange) {
                            //Logger.Info("{0}", e);
                            var t = e.AbsoluteTime;
                            var cce = e as ControlChangeEvent;
                        }
                        else if(e.CommandCode == MidiCommandCode.MetaEvent)
                        {
                            var timeEvent = e as TimeSignatureEvent;
                            if (timeEvent != null)
                            {
                                //Logger.Info("Time Event: {0}", timeEvent);
                                continue;
                            }

                            var setTempoEvent = e as TempoEvent;
                            if (setTempoEvent != null)
                            {
                                Logger.Info("TempoEvent: {0}", e);
                                ppqn = setTempoEvent.Tempo * 4;
                                //ppqn = setTempoEvent.MicrosecondsPerQuarterNote/1000f*2 ;
                                continue;
                            }

                            //Logger.Info("Command code: {0}", e);                                            
                        }
                        else {
                            //Logger.Info("more: {0}", e);
                            continue;
                        }

                        //var noteTime = (e.AbsoluteTime / TimeRate )-TimeOffset;                        
                        //Logger.Info(""+noteTime);
                        //if (noteTime < _lastTime || noteTime > context.Time)
                        //    continue;

                        //Logger.Info("here2");
                        //break;                            
                    }
                }
            }



            if (notes.Count <= 0) {
                return context;
            }
            numInstances = notes.Count;
            notes.Sort((s1, s2) => s1.StartTime.CompareTo(s2.StartTime));            

            var streamSize = numInstances * instanceDataSize;
            if (_instanceDataBuffer == null || _instanceDataBuffer.Description.SizeInBytes != streamSize)
            {
                Utilities.DisposeObj(ref _instanceDataBuffer);
                _instanceDataBuffer = new Buffer(context.D3DDevice, streamSize, ResourceUsage.Dynamic, BindFlags.VertexBuffer,
                                                 CpuAccessFlags.Write, ResourceOptionFlags.None, instanceDataSize);
            }

            DataStream instanceDataStream;
            context.D3DDevice.ImmediateContext.MapSubresource(_instanceDataBuffer, MapMode.WriteDiscard, MapFlags.None, out instanceDataStream);
            using (instanceDataStream)
            {
                instanceDataStream.Position = 0;
                var countInstances = 0;
                for (var index = 0; index < (int)numInstances; ++index)
                {
                    var note = notes[index]; //playedNoteEvents[numInstances-index-1];
                            
                    Matrix transform;                        
                    Vector3 t = new Vector3();

                    //var noteTime = (note.AbsoluteTime / TimeRate )-TimeOffset;

                    t = new Vector3((float) (note.StartTime),
                                    (float) (ScaleY * note.NoteNumber),
                                    (float) (note.TrackId*2));
                    transform = Matrix.Identity;
                        
                    var scale =  note.Velocity * (0.05f) + 0.001f;
                    var length = (float)(note.Duration/100f + 0.02f);
                    transform *= Matrix.Transformation(new Vector3(), new Quaternion(), new Vector3(length, 1, scale), new Vector3(), new Quaternion(), t);
                    transform *= prevTransform; 

                    instanceDataStream.Write(transform);
                    countInstances ++;
                }
            }
            context.D3DDevice.ImmediateContext.UnmapSubresource(_instanceDataBuffer, 0);

            var prevEffect = context.Effect;
            var prevRenderer = context.Renderer;
            context.Effect = _effect;
            context.Renderer = _renderer;

            try
            {
                _renderer.SetupEffect(context);

                if (context.DepthStencilView != null)
                    context.D3DDevice.ImmediateContext.OutputMerger.SetTargets(context.DepthStencilView, context.RenderTargetView);
                else
                    context.D3DDevice.ImmediateContext.OutputMerger.SetTargets(context.RenderTargetView);

                if (context.BlendState != null)
                {
                    context.D3DDevice.ImmediateContext.OutputMerger.BlendState = context.BlendState;
                }

                if (context.DepthStencilState != null)
                {
                    context.D3DDevice.ImmediateContext.OutputMerger.DepthStencilState = context.DepthStencilState;
                }

                if (context.RasterizerState != null)
                {
                    context.D3DDevice.ImmediateContext.Rasterizer.State = context.RasterizerState;
                }

                var technique = _effect.GetTechniqueByIndex(0);
                var pass = technique.GetPassByIndex(0);

                context.D3DDevice.ImmediateContext.Rasterizer.SetViewports(new [] { context.Viewport });
                context.D3DDevice.ImmediateContext.InputAssembler.InputLayout = new InputLayout(context.D3DDevice, pass.Description.Signature, _instanceDataInputElements);
                context.D3DDevice.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
                context.D3DDevice.ImmediateContext.InputAssembler.SetVertexBuffers(0, new [] { instancedMesh.Vertices, _instanceDataBuffer}, new [] {76, instanceDataSize}, new [] {0,0} );

                pass.Apply(context.D3DDevice.ImmediateContext);
                                
                //_effect.GetVariableByName("DisplaceMap").AsShaderResource().SetResource(DisplaceMapView);
                //var DisplaceMapView = new ShaderResourceView(context.D3DDevice, DisplaceMap);
                //_effect.GetVariableByName("InitialScale").AsVector().Set(new Vector2(InitialScaleX, InitialScaleY));
                    
                context.D3DDevice.ImmediateContext.DrawInstanced(instancedMesh.NumTriangles*3, numInstances, 0, 0);
            }
            catch (Exception exception)
            {
                Logger.Error(this,"GridWidthDisplace - An error occured during evaluation: {0}", exception.Message);
            }

            context.Effect = prevEffect;
            context.Renderer = prevRenderer;

            return context;
        }


        private OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier> _meshCollector = new OperatorPart.CollectOpPartFunctionsOfType<Framefield.Core.OperatorPartTraits.IMeshSupplier>();
        private Renderer _renderer;
        private Buffer _instanceDataBuffer;
        private InputElement[] _instanceDataInputElements = new []
                                                                {
                                                                    new InputElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0, 0),
                                                                    new InputElement("NORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 16, 0),
                                                                    new InputElement("COLOR", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 28, 0),
                                                                    new InputElement("TEXCOORD", 0, SharpDX.DXGI.Format.R32G32_Float, 44, 0),
                                                                    new InputElement("TANGENT", 0, SharpDX.DXGI.Format.R32G32B32_Float, 52, 0),
                                                                    new InputElement("BINORMAL", 0, SharpDX.DXGI.Format.R32G32B32_Float, 64, 0),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 0, SharpDX.DXGI.Format.R32G32B32A32_Float, 0,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 1, SharpDX.DXGI.Format.R32G32B32A32_Float, 16,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 2, SharpDX.DXGI.Format.R32G32B32A32_Float, 32,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                    new InputElement("INSTANCE_OBJ_TO_WORLD_ROW", 3, SharpDX.DXGI.Format.R32G32B32A32_Float, 48,
                                                                                     1, InputClassification.PerInstanceData, 1),
                                                                };
    }
}

