using System;

using Gamecraft.Wires;
using Svelto.ECS;
using Unity.Mathematics;

using GamecraftModdingAPI;
using GamecraftModdingAPI.Utility;

namespace GamecraftModdingAPI.Blocks
{
    /// <summary>
    /// Common implementation for blocks that support wiring.
    /// </summary>
	public class SignalingBlock : Block
	{
		public SignalingBlock(EGID id) : base(id)
		{
		}

		public SignalingBlock(uint id) : base(id)
		{
		}

        /// <summary>
        /// Generates the input port identifiers.
        /// </summary>
        /// <returns>The input identifiers.</returns>
		protected EGID[] GetInputIds()
		{
			return SignalEngine.GetSignalInputs(Id);
		}

        /// <summary>
        /// Generates the output port identifiers.
        /// </summary>
        /// <returns>The output identifiers.</returns>
		protected EGID[] GetOutputIds()
        {
            return SignalEngine.GetSignalOutputs(Id);
        }

        /// <summary>
        /// Gets the connected wire.
        /// </summary>
        /// <returns>The connected wire.</returns>
        /// <param name="portId">Port identifier.</param>
        /// <param name="connected">Whether the port has a wire connected to it.</param>
        protected ref WireEntityStruct GetConnectedWire(EGID portId, out bool connected)
		{
			return ref SignalEngine.MatchPortToWire(portId, Id, out connected);
		}

        /// <summary>
        /// [EXPERIMENTAL] Gets the channel data.
        /// </summary>
        /// <returns>The channel data.</returns>
        /// <param name="portId">Port identifier.</param>
        /// <param name="exists">Whether the channel actually exists.</param>
		protected ref ChannelDataStruct GetChannelData(EGID portId, out bool exists)
		{
			return ref SignalEngine.GetChannelDataStruct(portId, out exists);
		}

        /// <summary>
        /// The input port count.
        /// </summary>
        public uint InputCount
        {
	        get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.inputCount);
        }

        /// <summary>
        /// The output port count.
        /// </summary>
        public uint OutputCount
		{
	        get => BlockEngine.GetBlockInfo(this, (BlockPortsStruct st) => st.outputCount);
		}

        /// <summary>
        /// Connect an output on this block to an input on another block.
        /// </summary>
        /// <param name="sourcePort">Output port number.</param>
        /// <param name="destination">Input block.</param>
        /// <param name="destinationPort">Input port number.</param>
        /// <returns>The wire connection</returns>
        /// <exception cref="WiringException">The wire could not be created.</exception>
        public Wire Connect(byte sourcePort, SignalingBlock destination, byte destinationPort)
        {
	        if (sourcePort >= OutputCount)
	        {
		        throw new WiringException("Source port does not exist");
	        }

	        if (destinationPort >= destination.InputCount)
	        {
		        throw new WiringException("Destination port does not exist");
	        }
	        return Wire.Connect(this, sourcePort, destination, destinationPort);
        }

        /// <summary>
        /// The port's name.
        /// This is localized to the user's language, so this is not reliable for port identification.
        /// </summary>
        /// <param name="port">Port number.</param>
        /// <param name="input">Whether the port is an input (true) or an output (false).</param>
        /// <returns>The localized port name.</returns>
        public string PortName(byte port, bool input)
        {
	        BlockPortsStruct bps = BlockEngine.GetBlockInfo(this, (BlockPortsStruct a) => a);
	        PortEntityStruct pes = SignalEngine.GetPortByOffset(this, port, input);
	        return pes.portNameLocalised;
        }

        /// <summary>
        /// The input port's name.
        /// </summary>
        /// <param name="port">Input port number.</param>
        /// <returns>The port name, localized to the user's language.</returns>
        public string InputPortName(byte port) => PortName(port, true);
        
        /// <summary>
        /// The output port's name.
        /// </summary>
        /// <param name="port">Output port number.</param>
        /// <returns>The port name, localized to the user's language.</returns>
        public string OutputPortName(byte port) => PortName(port, false);

        /// <summary>
        /// All wires connected to the input port.
        /// These wires will always be wired output -> input.
        /// </summary>
        /// <param name="port">Port number.</param>
        /// <returns>Wires connected to the input port.</returns>
        public Wire[] ConnectedToInput(byte port)
        {
	        if (port >= InputCount) throw new WiringException($"Port input {port} does not exist");
	        EGID[] wireEgids = SignalEngine.WiredToInput(Id, port);
	        Wire[] wires = new Wire[wireEgids.Length];
	        for (uint i = 0; i < wireEgids.Length; i++)
	        {
		        wires[i] = new Wire(wireEgids[i]);
	        }
	        return wires;
        }
        
        /// <summary>
        /// All wires connected to the output port.
        /// These wires will always be wired output -> input.
        /// </summary>
        /// <param name="port">Port number.</param>
        /// <returns>Wires connected to the output port.</returns>
        public Wire[] ConnectedToOutput(byte port)
        {
	        if (port >= OutputCount) throw new WiringException($"Port output {port} does not exist");
	        EGID[] wireEgids = SignalEngine.WiredToOutput(Id, port);
	        Wire[] wires = new Wire[wireEgids.Length];
	        for (uint i = 0; i < wireEgids.Length; i++)
	        {
		        wires[i] = new Wire(wireEgids[i]);
	        }
	        return wires;
        }
	}
}