package org.artisanlogiciel.games.minetest.net;
import com.github.luben.zstd.Zstd;
import org.artisanlogiciel.games.minetest.MapBlock;
import org.artisanlogiciel.games.minetest.MapNode;
import org.artisanlogiciel.games.minetest.core.PacketException;
import org.artisanlogiciel.games.minetest.core.Serialize;
import org.artisanlogiciel.games.minetest.core.v2s16;
import org.artisanlogiciel.games.minetest.core.v3s16;

import java.nio.ByteBuffer;

/**
 * see src/network/networkprotocol.h of minetest sources
 *
 *
 * === NOTES ===
 *
 * A packet is sent through a channel to a peer with a basic header:
 * 	Header (7 bytes):
 * 	[0] u32 protocol_id
 * 	[4] session_t sender_peer_id
 * 	[6] u8 channel
 * sender_peer_id:
 * 	Unique to each peer.
 * 	value 0 (PEER_ID_INEXISTENT) is reserved for making new connections
 * 	value 1 (PEER_ID_SERVER) is reserved for server
 * 	these constants are defined in constants.h
 * channel:
 * 	Channel numbers have no intrinsic meaning. Currently only 0, 1, 2 exist.
 *
 *
 * */

public class PacketHandler {

    ReliablePacketBuffer reliableBuffer = null;

    IncomingSplitBuffer[] incomingChanneleSplitBuffer = new IncomingSplitBuffer[4];

    // server serialization in hello version
    int ser_version;

    // minetest protocol should be 0x4f457403
    static int readProtocolId(byte[] buffer, int length)
            throws PacketException
     {
        return Serialize.readU32(buffer, 0, length);
     }

    static int readPeerId(byte[] buffer, int length)
            throws PacketException
    {
        return Serialize.readU16(buffer,4,length);
    }

    static int readChannel(byte[] buffer, int length)
            throws PacketException
    {
        return Serialize.readU8(buffer, 6, length);
    }

    NetworkPacket handleAny(byte[] buffer, int length)
            throws PacketException
    {
        int protocol_id = readProtocolId(buffer, length);
        int peer_id = readPeerId(buffer, length);
        int channel = readChannel(buffer, length);
        int type = Serialize.readU8(buffer,7,length);
        PacketType packetType = PacketType.getPacketType(type);

        NetworkPacket networkPacket = new NetworkPacket(protocol_id,peer_id,channel,packetType, buffer, length);

        // offset of packet within buffer.
        networkPacket.addOffset(BufferedPacket.BASE_HEADER_SIZE);

        System.out.println("length " + length + " protocol_id " + String.format("%x", protocol_id) + " peer_id " + peer_id + " channel " + channel + " type " + packetType);

        return networkPacket;
    }

    void handlePacketType_Control(NetworkPacket networkPacket)
            throws PacketException {
    }

    void handlePacketType_Reliable(NetworkPacket networkPacket)
            throws PacketException
    {
        // TODO handle misordered...
        /*
        if ( reliableBuffer == null )
        {
            reliableBuffer = new ReliablePacketBuffer();
        }

        BufferedPacket packet = new BufferedPacket(buffer);
        int nextExpected = 0;
        reliableBuffer.insert(packet, nextExpected);
        */

        // handle nested packet
        networkPacket.addOffset(3);
        int offset = networkPacket.getOffset();
        byte buffer[] = networkPacket.getBuffer();
        int length = networkPacket.getLength();

        PacketType packetType = PacketType.getPacketType(Serialize.readU8(buffer,offset,length));

        System.out.println( "reliable " + packetType.toString());

        switch (packetType)
        {
            case PACKET_TYPE_CONTROL:
                //
                handlePacketType_Control(networkPacket);
                break;
            case PACKET_TYPE_ORIGINAL:
                handlePacketType_Original(networkPacket);
                break;
            case PACKET_TYPE_SPLIT:
                handlePacketType_Split(networkPacket);
                break;
            case PACKET_TYPE_RELIABLE:
                // this is an error, no nested reliable accepted.
                throw new PacketException("nested reliable");
            default: // error
                throw new PacketException("unknown type");
        }

    }

    void handleCommand_Hello(NetworkPacket networkPacket)
            throws PacketException {

        // u8 deployed serialisation version
        ByteBuffer buffer = networkPacket.getByteBuffer();
        int ser_ver = Serialize.readU8(buffer);
        ser_version = ser_ver;
        // u16 deployed network compression mode
        int net_compress = Serialize.readU16(buffer);
        // u16 deployed protocol version
        int deployed = Serialize.readU16(buffer);
        System.out.println(String.format("HELLO ser_ver=%d net_compress %d deployed %d", ser_ver,net_compress,deployed));
        // u32 supported auth methods
        // std::string username that should be used for legacy hash (for proper casing)

    }

    // clientpackethandler.cpp Client::handleCommand_BlockData(NetworkPacket* pkt)
    // TOCLIENT_BLOCKDATA
    void handleCommand_BlockData(NetworkPacket networkPacket)
            throws PacketException
    {
        v3s16 p = networkPacket.v3s16();

        // will be used to get vector...
        // v2s16 p2d = new v2s16(p.X,p.Z);

        System.out.println(String.format(" (X,Y,Z) %d %d %d", p.X, p.Y,p.Z));
        //
        networkPacket.addOffset(6);

        // FIXME get it from handshake
        int version = 28;

        // Should be a MapBlock
        MapBlock block = new MapBlock();
        block.deSerialize(networkPacket.getByteBuffer(),version);

        // now we have a block ! what to do with it ?
    }

    void handlePacketCommand(NetworkPacket networkPacket)
            throws PacketException {

        // consume type and ??? well ... 2
        int command = Serialize.readU16(networkPacket.getBuffer(),networkPacket.getOffset(),networkPacket.getLength());

        System.out.println(String.format("command %x length %d", command, networkPacket.getLength()));

        networkPacket.addOffset(2);
        // Original ... toClient - toServer
        switch (command)
        {
            case 0x02:
            {
                System.out.println("TOCLIENT_HELLO");
                handleCommand_Hello(networkPacket);
            }
            break;
            case 0x20:
            {
                System.out.println("TOCLIENT_BLOCKDATA");
                handleCommand_BlockData(networkPacket);
            }
            break;
            case 0x4f:
            {
                //TOCLIENT_SET_SKY = 0x4f,
                System.out.println("TOCLIENT_SET_SKY");
            }
            break;
        }

    }

    void handlePacketType_Original(NetworkPacket networkPacket)
            throws PacketException {

        // eat original type 1.
        networkPacket.addOffset(1);
        handlePacketCommand(networkPacket);
    }

    void handlePacketType_Split(NetworkPacket networkPacket)
            throws PacketException {

        int channel = networkPacket.getChannel();
        if ( channel < incomingChanneleSplitBuffer.length) {
            IncomingSplitBuffer splitBuffer = incomingChanneleSplitBuffer[channel];
            if ( splitBuffer == null )
            {
                splitBuffer = new IncomingSplitBuffer();
                incomingChanneleSplitBuffer[channel] = splitBuffer;
            }
            if ( splitBuffer != null ) {
                BufferedPacket bufferedPacket = splitBuffer.insert(networkPacket);
                if ( bufferedPacket != null ) {
                    // well should handle it.
                    System.out.println("Reassembled packet size " + bufferedPacket.m_data.length);
                    // reset it.
                    incomingChanneleSplitBuffer[channel] = new IncomingSplitBuffer();
                    handleBufferedPacket(bufferedPacket);
                }
            }
        }
        else {
            throw new PacketException("invalid channel " + channel);
        }


    }

    void handleBufferedPacket(BufferedPacket bufferedPacket)
            throws PacketException
    {
        // FIXME, why a BufferedPacket anyway since we finaly need a NetworkPacket ?
        NetworkPacket networkPacket = new NetworkPacket(0,0,0, PacketType.PACKET_TYPE_ORIGINAL,bufferedPacket.m_data, bufferedPacket.size());
        networkPacket.addOffset(BufferedPacket.BASE_HEADER_SIZE);
        handlePacketCommand(networkPacket);
    }

    void fromServer(byte[] buffer, int length)
            throws PacketException
    {
        System.out.print(" <- ");
        NetworkPacket networkPacket = handleAny(buffer,length);

        switch (networkPacket.packetType)
        {
            case PACKET_TYPE_CONTROL:
                //
                handlePacketType_Control(networkPacket);
                break;
            case PACKET_TYPE_RELIABLE:
                //
                handlePacketType_Reliable(networkPacket);
                break;
            case PACKET_TYPE_ORIGINAL:
                handlePacketType_Original(networkPacket);
                break;
            case PACKET_TYPE_SPLIT:
                handlePacketType_Split(networkPacket);
                break;
            default: // error
                throw new PacketException("unknown type");
        }

    }

    void fromClient(byte[] buffer, int length)
            throws PacketException
    {
        System.out.print(" -> ");
        handleAny(buffer,length);
    }

}
