package com.pwt.netty.common.charger;

import com.pwt.netty.common.Codeable;
import com.pwt.netty.common.charger.client.*;
import com.pwt.netty.common.charger.server.*;
import com.pwt.netty.handler.codec.exceptions.ProtocolException;
import com.pwt.netty.util.MessageUtil;
import io.netty.buffer.ByteBuf;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 充电机协议
 * @param <T> message data type.
 */
@Data
@Slf4j
@EqualsAndHashCode
public abstract class ChargerMessage<T extends MessageData> implements Codeable {

    public static final Map<Byte, Class<? extends MessageData>> MESSAGE_DATA_TYPE_MAP = new ConcurrentHashMap<>();

    static {
        // client request
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x10, ChargerChargeStatusData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x11, BatteryStatusData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x12, BatteryFaultData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x13, BmsStatusData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x14, BatteryOtherData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x15, ChargerStatusData.class);

        MESSAGE_DATA_TYPE_MAP.put((byte) 0x20, ChargeCompleteData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x30, ChargerAlarmData.class);

        // server response
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x16, ServerResponseData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x21, ServerResponseData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x31, ServerResponseData.class);

        // server request
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x50, StartStopCommand.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x70, ConfigurationCmdData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x80, ControlLightFlashCommand.class);
        MESSAGE_DATA_TYPE_MAP.put((byte) 0x90, CalibrateTimeCommand.class);

        // client response
        MESSAGE_DATA_TYPE_MAP.put((byte)0x51, ClientResponseData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte)0x71, ClientResponseData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte)0x81, ClientResponseData.class);
        MESSAGE_DATA_TYPE_MAP.put((byte)0x91, ClientResponseData.class);
    }

    /**
     * 消息类型长度
     */
    public static final int TYPE_LENGTH = 1;

    /**
     * length字段长度
     */
    public static final int LENGTH_FIELD_LENGTH = 2;

    /**
     * length偏移量
     */
    public static final int LENGTH_FIELD_OFFSET = 1;

    /**
     * 流水号长度
     */
    public static final int STREAM_ID_LENGTH = 4;

    /**
     * 充电机地址长度
     */
    public static final int CHARGER_ADDRESS_LENGTH = 13;

    /**
     * 操作码
     */
    private int type;

    /**
     * 长度
     */
    private int length;
    /**
     * 流水号
     */
    private int streamId = (int) (System.currentTimeMillis() / 1000);

    /**
     * 充电机地址
     */
    private String chargerAddress;

    /**
     * 枪号, '1' 代表第一台枪, '2' 代表第二台枪。以此类推
     */
    private char gunNo;

    public String getChargerGunId() {
        return (chargerAddress == null ? "" : chargerAddress) + gunNo;
    }


    private T messageData;

    // TODO
//    public ChargerMessage(T messageData) {
//        this.messageData = messageData;
//    }

    @Override
    public final void encode(ByteBuf byteBuf) throws Exception {
        int zeroWriterIndex = byteBuf.writerIndex();
        encodeHeader(byteBuf);
        int dataWriteStartIndex = byteBuf.writerIndex();
        messageData.encode(byteBuf);
        int dataWriteEndIndex = byteBuf.writerIndex();
        byteBuf.markWriterIndex();

        // 计算并写入长度: 写入长度
        int length = dataWriteEndIndex - dataWriteStartIndex;
        byteBuf.writerIndex(zeroWriterIndex + LENGTH_FIELD_OFFSET);
        byteBuf.writeShort(length);
        byteBuf.resetWriterIndex();
    }

    @Override
    public final void decode(ByteBuf byteBuf) throws Exception {
        if (byteBuf != null) {
            log.info("充电机原始报文：{}", MessageUtil.convertByteBufToString(byteBuf));
        }
        decodeHeader(byteBuf);
        messageData.decode(byteBuf);
    }

    private void encodeHeader(ByteBuf byteBuf) {
        byteBuf.writeByte(type);
        byteBuf.writeShort(0);
        byteBuf.writeInt(streamId);
        byteBuf.writeBytes(getChargerAddress().getBytes(StandardCharsets.US_ASCII));
        byteBuf.writeByte(gunNo);
    }

    private void decodeHeader(ByteBuf byteBuf) throws ProtocolException {
        type = byteBuf.readUnsignedByte();
        length = byteBuf.readUnsignedShort();
        if(length != this.messageData.getProtocolLength()) {
            throw new ProtocolException(String.format("raw length value {%s} not match protocol length {%s}. type {%x}", length, messageData.getProtocolLength(), type));
        }
        streamId = byteBuf.readInt();
        chargerAddress = byteBuf.readCharSequence(CHARGER_ADDRESS_LENGTH, StandardCharsets.US_ASCII).toString();
        gunNo = (char) byteBuf.readByte();
    }

    public String getChargerAddress() {
        return chargerAddress != null ? chargerAddress : StringUtils.repeat("0", CHARGER_ADDRESS_LENGTH);
    }
}
