/*-
 * <<
 * DBus
 * ==
 * Copyright (C) 2016 - 2019 Bridata
 * ==
 * Licensed 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
 * 
 *      http://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 com.creditease.dbus.stream.oracle.dispatcher;

import com.creditease.dbus.stream.common.tools.IGenericMessage;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.*;
import org.apache.avro.util.Utf8;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import java.util.List;

/**
 * osource generic schema getInstance
 * Created by Shrimp on 16/5/5.
 */
public class OracleGenericSchemaDecoder {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private Schema genericSchema;
    private Schema fullPullSchema;
    private int fullPullHash;
    private Schema heartbeatSchema;
    private int heartbeatHash;
    private Schema syncEventSchema;
    private int syncEventHash;

    private GenericDatumReader<GenericRecord> datumReader;
    private GenericDatumWriter<GenericRecord> datumWriter;
    private static OracleGenericSchemaDecoder instance = new OracleGenericSchemaDecoder();

    private OracleGenericSchemaDecoder() {
        initDecoder();
    }

    public static OracleGenericSchemaDecoder getInstance() {
        return instance;
    }

    private void initDecoder() {
        try {
            genericSchema = OracleGenericSchemaProvider.getInstance().getSchema("generic_wrapper.avsc");

            fullPullSchema = OracleGenericSchemaProvider.getInstance().getSchema("DBUS.DB_FULL_PULL_REQUESTS.avsc");
            fullPullHash = OracleGenericSchemaProvider.getInstance().getSchemaHash("DBUS.DB_FULL_PULL_REQUESTS.avsc");

            syncEventSchema = OracleGenericSchemaProvider.getInstance().getSchema("DBUS.META_SYNC_EVENT.avsc");
            syncEventHash = OracleGenericSchemaProvider.getInstance().getSchemaHash("DBUS.META_SYNC_EVENT.avsc");

            heartbeatSchema = OracleGenericSchemaProvider.getInstance().getSchema("DBUS.DB_HEARTBEAT_MONITOR.avsc");
            heartbeatHash = OracleGenericSchemaProvider.getInstance().getSchemaHash("DBUS.DB_HEARTBEAT_MONITOR.avsc");

            datumReader = new GenericDatumReader<>(genericSchema);
            datumWriter = new GenericDatumWriter<>(genericSchema);
        } catch (Exception e) {
            logger.error("OracleGenericSchemaDecoder Initialization Error!", e);
            e.printStackTrace();

        }
    }

    /**
     * 解析被generic schema封装的实际数据
     *
     * @param schema  schema对象
     * @param payload 实际数据
     * @return List<GenericRecord>
     * @throws Exception
     */
    public List<GenericRecord> decode(Schema schema, byte[] payload) throws IOException {
        logger.debug("Schema:" + schema.toString() + " schema payload:" + new String(payload, "utf-8"));
        List<GenericRecord> list = new LinkedList<>();
        DatumReader<GenericRecord> reader = new GenericDatumReader<>(schema);
        BinaryDecoder decoder = getBinaryDecoder(payload);
        while (!decoder.isEnd()) {
            list.add(reader.read(null, decoder));
        }
        return list;
    }

    public List<GenericRecord> decodeSyncEvent(int hash, byte[] payload) throws IOException {
        if (syncEventHash != hash) {
            throw new RuntimeException(String.format("syncEvent schema hash 不一致, 期待的是%s, 实际是%s", syncEventHash, hash));
        }
        return decode(syncEventSchema, payload);
    }

    public List<GenericRecord> decodeFullPull(int hash, byte[] payload) throws IOException {
        if (fullPullHash != hash) {
            throw new RuntimeException(String.format("fullPull schema hash 不一致, 期待的是%s, 实际是%s", fullPullHash, hash));
        }
        return decode(fullPullSchema, payload);
    }

    public List<GenericRecord> decodeHeartBeat(int hash, byte[] payload) throws IOException {
        if (heartbeatHash != hash) {
            throw new RuntimeException(String.format("HeartBeat schema hash 不一致, 期待的是%s, 实际是%s", heartbeatHash, hash));
        }
        return decode(heartbeatSchema, payload);
    }

    public byte[] wrap(List<IGenericMessage> list) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        BinaryEncoder encoder = EncoderFactory.get().blockingBinaryEncoder(outputStream, null);

        for (IGenericMessage obj : list) {
            OracleGenericMessage msg = (OracleGenericMessage) obj;
            GenericRecord record = msg.generateRecord(genericSchema);
            datumWriter.write(record, encoder);
        }
        encoder.flush();

        return outputStream.toByteArray();
    }

    public List<IGenericMessage> unwrap(byte[] input) throws IOException {
        List<IGenericMessage> list = new LinkedList<>();

        BinaryDecoder decoder = getBinaryDecoder(input);
        while (!decoder.isEnd()) {
            GenericRecord record = datumReader.read(null, decoder);

            OracleGenericMessage msg = new OracleGenericMessage();

            Utf8 utf8 = (Utf8) record.get(OracleGenericMessage.NAMESAPCE);
            msg.setNameSpace(utf8.toString());
            msg.setSchemaHash((Integer) record.get(OracleGenericMessage.SCHEMA_HASH));
            ByteBuffer buffer = (ByteBuffer) record.get(OracleGenericMessage.PAYLOAD);
            msg.setPayload(buffer.array());

            logger.debug(String.format("TAble: %s, HASH: %d\n", msg.getNameSpace(), msg.getSchemaHash()));

            list.add((IGenericMessage) msg);
        }

        return list;
    }

    private BinaryDecoder getBinaryDecoder(byte[] bytes) {
        return DecoderFactory.get().binaryDecoder(bytes, null);
    }
}