using Svelto.ECS;
using Gamecraft.Wires;

using GamecraftModdingAPI.Engines;

namespace GamecraftModdingAPI.Blocks
{
    /// <summary>
    /// Engine which executes signal actions
    /// </summary>
    public class SignalEngine : IApiEngine
    {
		public const float POSITIVE_HIGH = 1.0f;
		public const float NEGATIVE_HIGH = -1.0f;
		public const float HIGH = 1.0f;
		public const float ZERO = 0.0f;

        public string Name { get; } = "GamecraftModdingAPISignalGameEngine";

        public EntitiesDB entitiesDB { set; private get; }
        
		public bool isRemovable => false;

		public bool IsInGame = false;

        public void Dispose()
        {
            IsInGame = false;
        }

        public void Ready()
        {
            IsInGame = true;
        }

        // implementations for Signal static class

        public bool SetSignal(EGID blockID, float signal, out uint signalID, bool input = true)
        {
            signalID = GetSignalIDs(blockID, input)[0];
            return SetSignal(signalID, signal);
        }

        public bool SetSignal(uint signalID, float signal, bool input = true)
        {
	        var array = GetSignalStruct(signalID, out uint index, input);
	        if (array != null) array[index].valueAsFloat = signal;
            return false;
        }

        public float AddSignal(EGID blockID, float signal, out uint signalID, bool clamp = true, bool input = true)
        {
            signalID = GetSignalIDs(blockID, input)[0];
            return AddSignal(signalID, signal, clamp, input);
        }

        public float AddSignal(uint signalID, float signal, bool clamp = true, bool input = true)
        {
	        var array = GetSignalStruct(signalID, out uint index, input);
	        if (array != null)
	        {
		        ref var channelData = ref array[index];
		        channelData.valueAsFloat += signal;
		        if (clamp)
		        {
			        if (channelData.valueAsFloat > POSITIVE_HIGH)
			        {
				        channelData.valueAsFloat = POSITIVE_HIGH;
			        }
			        else if (channelData.valueAsFloat < NEGATIVE_HIGH)
			        {
				        channelData.valueAsFloat = NEGATIVE_HIGH;
			        }

			        return channelData.valueAsFloat;
		        }
	        }

	        return signal;
        }

        public float GetSignal(EGID blockID, out uint signalID, bool input = true)
        {
			signalID = GetSignalIDs(blockID, input)[0];
            return GetSignal(signalID, input);
        }

        public float GetSignal(uint signalID, bool input = true)
        {
	        var array = GetSignalStruct(signalID, out uint index, input);
	        return array?[index].valueAsFloat ?? 0f;
        }

        public uint[] GetSignalIDs(EGID blockID, bool input = true)
		{
			ref BlockPortsStruct bps = ref entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
			uint[] signals;
			if (input) {
				signals = new uint[bps.inputCount];
				for (uint i = 0u; i < bps.inputCount; i++)
				{
					signals[i] = bps.firstInputID + i;
				}
			} else {
				signals = new uint[bps.outputCount];
                for (uint i = 0u; i < bps.outputCount; i++)
                {
                    signals[i] = bps.firstOutputID + i;
                }
			}
			return signals;
		}

        public EGID[] GetSignalInputs(EGID blockID)
		{
			BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
			EGID[] inputs = new EGID[ports.inputCount];
			for (uint i = 0; i < ports.inputCount; i++)
			{
				inputs[i] = new EGID(i + ports.firstInputID, NamedExclusiveGroup<InputPortsGroup>.Group);
			}
			return inputs;
		}

		public EGID[] GetSignalOutputs(EGID blockID)
        {
            BlockPortsStruct ports = entitiesDB.QueryEntity<BlockPortsStruct>(blockID);
            EGID[] outputs = new EGID[ports.outputCount];
            for (uint i = 0; i < ports.outputCount; i++)
            {
                outputs[i] = new EGID(i + ports.firstOutputID, NamedExclusiveGroup<OutputPortsGroup>.Group);
            }
            return outputs;
        }

		public ref WireEntityStruct MatchPortToWire(EGID portID, EGID blockID, out bool exists)
		{
			ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
			WireEntityStruct[] wires = entitiesDB.QueryEntities<WireEntityStruct>(NamedExclusiveGroup<WiresGroup>.Group).ToFastAccess(out uint count);
			for (uint i = 0; i < count; i++)
			{
				if ((wires[i].destinationPortUsage == port.usage && wires[i].destinationBlockEGID == blockID)
				    || (wires[i].sourcePortUsage == port.usage && wires[i].sourceBlockEGID == blockID))
				{
					exists = true;
					return ref wires[i];
				}
			}
			exists = false;
			WireEntityStruct[] defRef = new WireEntityStruct[1];
            return ref defRef[0];
		}

        public ref ChannelDataStruct GetChannelDataStruct(EGID portID, out bool exists)
		{
			ref PortEntityStruct port = ref entitiesDB.QueryEntity<PortEntityStruct>(portID);
			ChannelDataStruct[] channels = entitiesDB.QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group).ToFastAccess(out uint count);
			if (port.firstChannelIndexCachedInSim < count)
			{
				exists = true;
				return ref channels[port.firstChannelIndexCachedInSim];
			}
			exists = false;
			ChannelDataStruct[] defRef = new ChannelDataStruct[1];
			return ref defRef[0];
		}

        public EGID[] GetElectricBlocks()
		{
			uint count = entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS) + entitiesDB.Count<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS);
            uint i = 0;
            EGID[] res = new EGID[count];
			foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.OWNED_BLOCKS))
            {
                res[i] = s.ID;
                i++;
            }
			foreach (ref BlockPortsStruct s in entitiesDB.QueryEntities<BlockPortsStruct>(BlockIdentifiers.FUNCTIONAL_BLOCK_PARTS))
            {
                res[i] = s.ID;
                i++;
            }
            return res;
        }

        private ChannelDataStruct[] GetSignalStruct(uint signalID, out uint index, bool input = true)
        {
	        ExclusiveGroup group = input
		        ? NamedExclusiveGroup<InputPortsGroup>.Group
		        : NamedExclusiveGroup<OutputPortsGroup>.Group;
	        if (entitiesDB.Exists<PortEntityStruct>(signalID, group))
	        {
		        index = entitiesDB.QueryEntity<PortEntityStruct>(signalID, group).anyChannelIndex;
		        ChannelDataStruct[] channelData = entitiesDB
			        .QueryEntities<ChannelDataStruct>(NamedExclusiveGroup<ChannelDataGroup>.Group)
			        .ToFastAccess(out uint _);
		        return channelData;
	        }

	        index = 0;
	        return null;
        }
    }
}