/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.plc4x.java.spi;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageCodec;
import org.apache.commons.codec.binary.Hex;
import org.apache.plc4x.java.spi.generation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

public abstract class GeneratedDriverByteToMessageCodec<T extends Message> extends ByteToMessageCodec<T> {

    private static final Logger LOGGER = LoggerFactory.getLogger(GeneratedDriverByteToMessageCodec.class);

    private final ByteOrder byteOrder;
    private final MessageInput<T> messageInput;
    private final MessageOutput<T> messageOutput;

    protected GeneratedDriverByteToMessageCodec(MessageInput<T> messageInput, MessageOutput<T> messageOutput,
                                                Class<T> outboundMessageType, ByteOrder byteOrder) {
        super(outboundMessageType);
        this.messageInput = messageInput;
        this.messageOutput = messageOutput;
        this.byteOrder = byteOrder;
    }

    @Override
    protected void encode(ChannelHandlerContext ctx, T packet, ByteBuf byteBuf) {
        try {
            WriteBufferByteBased buffer;
            if(messageOutput != null) {
                buffer = messageOutput.serialize(packet);
            } else {
                buffer = new WriteBufferByteBased(packet.getLengthInBytes(), byteOrder);
                packet.serialize(buffer);
            }
            byteBuf.writeBytes(buffer.getBytes());
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Sending bytes to PLC for message {} as data {}", packet, Hex.encodeHexString(buffer.getBytes()));
            }
        } catch (Exception e) {
            LOGGER.warn("Error encoding package [{}]: {}", packet, e.getMessage(), e);
        }
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) {
        LOGGER.trace("Receiving bytes, trying to decode Message...");
        // As long as there is data available, continue checking the content.
        while (byteBuf.readableBytes() > 0) {
            byte[] bytes = null;
            try {
                // Check if enough data is present to process the entire package.
                int packetSize = getPacketSize(byteBuf);
                if (packetSize == -1 || packetSize > byteBuf.readableBytes()) {
                    return;
                }

                // Read the packet data into a new ReadBuffer
                bytes = new byte[packetSize];
                byteBuf.readBytes(bytes);
                ReadBuffer readBuffer = new ReadBufferByteBased(bytes, byteOrder);

                // Parse the packet.
                T packet = messageInput.parse(readBuffer);

                // Pass the packet to the pipeline.
                out.add(packet);

                // It seems that one batch of 16 messages is the maximum, so we have to give up
                // and process the rest next time.
                if (out.size() >= 16) {
                    return;
                }
            } catch (Exception e) {
                if (bytes != null) {
                    LOGGER.warn("Error decoding package with content [{}]: {}",
                        Hex.encodeHexString(bytes), e.getMessage(), e);
                }
                // Just remove any trailing junk ... if there is any.
                removeRestOfCorruptPackage(byteBuf);
            }
        }
    }

    protected abstract int getPacketSize(ByteBuf byteBuf);

    protected abstract void removeRestOfCorruptPackage(ByteBuf byteBuf);

}
