package meerkat.util;

import com.google.protobuf.ByteString;
import com.google.protobuf.Int64Value;
import meerkat.protobuf.BulletinBoardApi.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * Created by Arbel Deutsch Peled on 16-Feb-16.
 */
public class BulletinBoardUtils {

    /**
     * Searches the tags in the message for one that begins with given prefix
     * @param message is the message to search
     * @param prefix is the given prefix
     * @return the tag without the prefix, if found, or null if not found
     */
    public static String findTagWithPrefix(BulletinBoardMessage message, String prefix) {

        for (String tag : message.getMsg().getTagList()){
            if (tag.startsWith(prefix)) {
                return tag.substring(prefix.length());
            }
        }

        return null;

    }

    /**
     * Searches the tags in a message for tags that do not contain a given list of prefixes
     * @param message is the message to search
     * @param prefixes is the list of prefixes
     * @return a list of the tags that do *not* contain any of the given prefixes
     */
    public static List<String> removePrefixTags(BulletinBoardMessage message, Iterable<String> prefixes) {

        if (prefixes == null)
            return message.getMsg().getTagList();

        List<String> result = new LinkedList<>();

        for (String tag : message.getMsg().getTagList()){

            boolean found = false;

            for (String prefix : prefixes){
                if (tag.startsWith(prefix)){
                    found = true;
                    break;
                }
            }

            if (!found) {
                result.add(tag);
            }

        }

        return result;

    }

    /**
     * This method creates a Timestamp Protobuf from a time specification
     * @param timeInMillis is the time to encode since the Epoch time in milliseconds
     * @return a Timestamp Protobuf encoding of the given time
     */
    public static com.google.protobuf.Timestamp toTimestampProto(long timeInMillis) {

        return com.google.protobuf.Timestamp.newBuilder()
                .setSeconds(timeInMillis / 1000)
                .setNanos((int) ((timeInMillis % 1000) * 1000000))
                .build();

    }

    /**
     * This method creates a Timestamp Protobuf from the current system time
     * @return a Timestamp Protobuf encoding of the current system time
     */
    public static com.google.protobuf.Timestamp getCurrentTimestampProto() {

        return toTimestampProto(System.currentTimeMillis());

    }

    /**
     * This method converts an SQL Timestamp object into a Protobuf Timestamp object
     * @param sqlTimestamp is the SQL Timestamp
     * @return an equivalent Protobuf Timestamp
     */
    public static com.google.protobuf.Timestamp toTimestampProto(java.sql.Timestamp sqlTimestamp) {
        return toTimestampProto(sqlTimestamp.getTime());
    }

    /**
     * This method converts a Protobuf Timestamp object into an SQL Timestamp object
     * @param protoTimestamp is the Protobuf Timestamp
     * @return an equivalent SQL Timestamp
     */
    public static java.sql.Timestamp toSQLTimestamp(com.google.protobuf.Timestamp protoTimestamp) {
        return new java.sql.Timestamp(protoTimestamp.getSeconds() * 1000 + protoTimestamp.getNanos() / 1000000);
    }

    /**
     * Breaks up a bulletin board message into chunks
     * @param msg is the complete message
     * @return a list of BatchChunks that contains the raw message data
     */
    public static List<BatchChunk> breakToBatch(BulletinBoardMessage msg, int chunkSize) {

        byte[] data = msg.getMsg().getData().toByteArray();

        int chunkNum = data.length / chunkSize;
        if (data.length % chunkSize != 0)
            chunkNum++;

        List<BatchChunk> chunkList = new ArrayList<>(chunkNum);

        int location = 0;

        for (int i=0 ; i < chunkNum ; i++) {

            int chunkLength;

            if (i == chunkNum - 1){
                chunkLength = data.length % chunkSize;
                if (chunkLength == 0){
                    chunkLength = chunkSize;
                }
            } else{
                chunkLength = chunkSize;
            }

            chunkList.add(BatchChunk.newBuilder()
                    .setData(ByteString.copyFrom(data, location, chunkLength))
                    .build());

            location += chunkLength;

        }

        return chunkList;

    }

    /**
     * Removes concrete data from the message and turns it into a stub
     * Note that the stub does not contain the message ID
     * Therefore, it cannot be used to retrieve the message from a server
     * @param msg is the original message
     * @return the message stub
     */
    public static BulletinBoardMessage makeStub(BulletinBoardMessage msg) {

        return BulletinBoardMessage.newBuilder()
                .mergeFrom(msg)
                .setMsg(UnsignedBulletinBoardMessage.newBuilder()
                        .mergeFrom(msg.getMsg())
                        .clearDataType()
                        .clearData()
                        .build())
                .build();

    }

    /**
     * Merges a batch chunk list back into a message stub to create a complete Bulletin Board message
     * @param msgStub is a message stub
     * @param chunkList contains the (ordered) data of the batch message
     * @return a complete message containing both data and metadata
     */
    public static BulletinBoardMessage gatherBatch(BulletinBoardMessage msgStub, List<BatchChunk> chunkList) {

        List<ByteString> dataList = new LinkedList<>();

        for (BatchChunk chunk : chunkList){
            dataList.add(chunk.getData());
        }

        return BulletinBoardMessage.newBuilder()
                .mergeFrom(msgStub)
                .setMsg(UnsignedBulletinBoardMessage.newBuilder()
                        .mergeFrom(msgStub.getMsg())
                        .setData(ByteString.copyFrom(dataList))
                        .build())
                .build();

    }

    /**
     * Gerenates a BeginBatchMessage Protobuf which is used to begin uploading a message as a batch
     * @param msg is the Bulletin Board message to be uploaded, which can be a stub or a complete message
     * @return the required BeginBatchMessage
     */
    public static BeginBatchMessage generateBeginBatchMessage(BulletinBoardMessage msg) {

        if (msg.getSigCount() <= 0){
            throw new IllegalArgumentException("No signatures found");
        }

        return BeginBatchMessage.newBuilder()
                .addAllTag(msg.getMsg().getTagList())
                .build();

    }

    /**
     * Gerenates a CloseBatchMessage Protobuf which is used to finalize a batch message
     * @param batchId is the temporary identifier for the message
     * @param batchLength is the number of chunks in the batch
     * @param msg is the Bulletin Board message that was uploaded (and can also be a stub of said message)
     * @throws IllegalArgumentException if the message contains no signatures
     */
    public static CloseBatchMessage generateCloseBatchMessage(Int64Value batchId, int batchLength, BulletinBoardMessage msg) {

        if (msg.getSigCount() <= 0){
            throw new IllegalArgumentException("No signatures found");
        }

        return CloseBatchMessage.newBuilder()
                .setTimestamp(msg.getMsg().getTimestamp())
                .setBatchLength(batchLength)
                .setBatchId(batchId.getValue())
                .addAllSig(msg.getSigList())
                .build();

    }

}
