using NIFPGA.lvbitx;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using FxpConvert.Common;

namespace NIFPGA
{
    public sealed class FPGA
    {
        private List<Fifo> fifos= new List<Fifo>();
        private IFxpConvert _FXPConvert;
        private List<FPGABaseProperty> properties= new List<FPGABaseProperty>();

        public IReadOnlyDictionary<String,Fifo> Fifos => fifos.ToDictionary(x=>x.Name);
        public IReadOnlyDictionary<String,FPGABaseProperty> Properties=> properties.ToDictionary(x=>x.Name);
        private FPGASession session;
        public Boolean Error=> session.Error;
        public string Message=>session.Message;
        public FPGA(IFxpConvert convert)
        {
            _FXPConvert = convert;
            session = new FPGASession();
            Irq = new Irq(session);
        }
        public T CreateProperty<T>(string name) where T :FPGABaseProperty
        {
            var pro = properties.FirstOrDefault(x => x.Name == name);
            if (pro == null) throw new Exception($"Property {name} not found");
            return (T)pro;
        }
        public T CreateFifo<T>(string name) where T:Fifo
        {
            var fifo = fifos.FirstOrDefault(x => x.Name == name);
            if (fifo == null) throw new Exception($"Fifo {name} not found");
            return (T)fifo;
        }
        public void Parse(string path)
        {
            var val = lvbitx.Parse.ParseFile(path);
            InitProperies(val.VI.Registers);
            InitFifo(val.Project.DMA);
        }
        
        private void InitFifo(List<DMA> dma)
        {
            if (dma == null || dma.Count == 0) return;
            dma.ForEach(x =>
            {
                switch(x.Direction)
                {
                    case "TargetToHost"://read
                        switch (x.Datatype)
                        {
                            case Datatype.Boolean:
                                fifos.Add(new ReadFifo<bool>(session, x.ControlSet)
                                {
                                    Name = x.Name,
                                });
                                break;
                            case Datatype.Int8:
                                fifos.Add(new ReadFifo<sbyte>(session, x.ControlSet)
                                {
                                    Name = x.Name,
                                });
                                break;
                            case Datatype.Uint8:
                                fifos.Add(new ReadFifo<byte>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int16:
                                fifos.Add(new ReadFifo<short>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint16:
                                fifos.Add(new ReadFifo<ushort>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int32:
                                fifos.Add(new ReadFifo<int>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint32:
                                fifos.Add(new ReadFifo<uint>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int64:
                                fifos.Add(new ReadFifo<long>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint64:
                                fifos.Add(new ReadFifo<ulong>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Float:
                                fifos.Add(new ReadFifo<float>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Double:
                                fifos.Add(new ReadFifo<double>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.FXP:
                                fifos.Add(new ReadFXPFifo(session, x.ControlSet,x.FxpTypeInfo, _FXPConvert)
                                {
                                    Name = x.Name,
                                });
                                break;
                            case Datatype.Array:
                                break;
                        }
                        break;
                    case "HostToTarget"://write
                        switch (x.Datatype)
                        {
                            case Datatype.Boolean:
                                fifos.Add(new WriteFifo<bool>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int8:
                                fifos.Add(new WriteFifo<sbyte>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint8:
                                fifos.Add(new WriteFifo<byte>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int16:
                                fifos.Add(new WriteFifo<short>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint16:
                                fifos.Add(new WriteFifo<ushort>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int32:
                                fifos.Add(new WriteFifo<int>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint32:
                                fifos.Add(new WriteFifo<uint>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Int64:
                                fifos.Add(new WriteFifo<long>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Uint64:
                                fifos.Add(new WriteFifo<ulong>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Float:
                                fifos.Add(new WriteFifo<float>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.Double:
                                fifos.Add(new WriteFifo<double>(session, x.ControlSet) { Name = x.Name });
                                break;
                            case Datatype.FXP:
                                fifos.Add(new WriteFXPFifo(session, x.ControlSet, x.FxpTypeInfo, _FXPConvert) { Name = x.Name });
                                break;
                            case Datatype.Array:
                                break;
                        }
                        break;
                }
            });
        }
        public Irq Irq { get; }
        public void Open(string bitfile, string resource, NiFpga_OpenAttribute attribute)
        {
            var lv = lvbitx.Parse.ParseFile(bitfile);
            InitProperies(lv.VI.Registers);
            InitFifo(lv.Project.DMA);
            session.Open(bitfile, lv.SignatureRegister, resource, attribute);
        }
        private void InitProperies(List<lvbitx.Register> registers)
        {
            if (registers == null || registers.Count == 0) return;

            foreach (var reg in registers)
            {
                switch (reg.Datatype)
                {
                    case lvbitx.Datatype.Int16:
                        properties.Add(new FPGAProperty<short>(session, reg.Offset, reg.Indicator)
                        {
                            Name= reg.Name,
                        });
                        break;
                    case lvbitx.Datatype.Boolean:
                        properties.Add(new FPGAProperty<bool>(session, reg.Offset, reg.Indicator)
                        {
                            Name = reg.Name,
                        });
                        break;
                    case lvbitx.Datatype.Int8:
                        properties.Add(new FPGAProperty<sbyte>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Uint8:
                        properties.Add(new FPGAProperty<byte>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Uint16:
                        properties.Add(new FPGAProperty<ushort>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Int32:
                        properties.Add(new FPGAProperty<int>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Uint32:
                        properties.Add(new FPGAProperty<uint>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Int64:
                        properties.Add(new FPGAProperty<long>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Uint64:
                        properties.Add(new FPGAProperty<ulong>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Float:
                        properties.Add(new FPGAProperty<float>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.Double:
                        properties.Add(new FPGAProperty<double>(session, reg.Offset, reg.Indicator) { Name = reg.Name });
                        break;
                    case lvbitx.Datatype.FXP:
                        properties.Add(new FPGAFXPProperty(session, reg.Offset, reg.FxpTypeInfo,_FXPConvert, reg.Indicator)
                        {
                            Name = reg.Name,
                        });
                        break;
                    case lvbitx.Datatype.Array:
                        switch (reg.ArrayValueType)
                        {
                            case lvbitx.Datatype.Boolean:
                                properties.Add(new FPGAArrayProperty<bool>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Int8:
                                properties.Add(new FPGAArrayProperty<sbyte>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Uint8:
                                properties.Add(new FPGAArrayProperty<byte>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Int16:
                                properties.Add(new FPGAArrayProperty<short>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Uint16:
                                properties.Add(new FPGAArrayProperty<short>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Int32:
                                properties.Add(new FPGAArrayProperty<int>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Uint32:
                                properties.Add(new FPGAArrayProperty<uint>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Int64:
                                properties.Add(new FPGAArrayProperty<long>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Uint64:
                                properties.Add(new FPGAArrayProperty<ulong>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Float:
                                properties.Add(new FPGAArrayProperty<float>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Double:
                                properties.Add(new FPGAArrayProperty<double>(session, reg.Offset, reg.Size, reg.Indicator) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.FXP:
                                properties.Add(new FPGAArrayFXPProperty(session, reg.Offset, reg.Size, reg.FxpTypeInfo,_FXPConvert) { Name = reg.Name });
                                break;
                            case lvbitx.Datatype.Array:
                                break;
                        }
                        break;
                }
            }
        }
        public void Close()=>session.Close();
        public void Run()
        {
            session.CheckResult(session.GetDelegate<Interop.NiFpgaDll_Run>()(session.Session, Interop.NiFpga_RunAttribute.NiFpga_RunAttribute_None));
        }
        public void Abort()
        {
            session.CheckResult(session.GetDelegate<Interop.NiFpgaDll_Abort>()(session.Session));
        }
        public void Reset()
        {
            session.CheckResult(session.GetDelegate<Interop.NiFpgaDll_Reset>()(session.Session));
        }
        public void Download()
        {
            session.CheckResult(session.GetDelegate<Interop.NiFpgaDll_Download>()(session.Session));
        }
    }
}
