import com.google.protobuf.ByteString;
import meerkat.bulletinboard.BulletinBoardClient;
import meerkat.bulletinboard.BulletinBoardClient.ClientCallback;
import meerkat.bulletinboard.ThreadedBulletinBoardClient;
import meerkat.protobuf.BulletinBoardAPI.*;
import meerkat.protobuf.Crypto;

import meerkat.protobuf.Voting.*;
import meerkat.util.BulletinBoardMessageComparator;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.number.OrderingComparison.*;

import java.util.*;
import java.util.concurrent.Semaphore;

/**
 * Created by Arbel Deutsch Peled on 05-Dec-15.
 */
public class BulletinBoardClientIntegrationTest {

    Semaphore jobSemaphore;
    Vector<Throwable> thrown;

    private class PostCallback implements ClientCallback<Object>{

        @Override
        public void handleCallback(Object msg) {
            System.err.println("Post operation completed");
            jobSemaphore.release();
        }

        @Override
        public void handleFailure(Throwable t) {
            thrown.add(t);
            jobSemaphore.release();
        }
    }

    private class RedundancyCallback implements ClientCallback<Float>{

        private float minRedundancy;

        public RedundancyCallback(float minRedundancy) {
            this.minRedundancy = minRedundancy;
        }

        @Override
        public void handleCallback(Float redundancy) {
            System.err.println("Redundancy found is: " + redundancy);
            jobSemaphore.release();
            assertThat(redundancy, greaterThanOrEqualTo(minRedundancy));
        }

        @Override
        public void handleFailure(Throwable t) {
            thrown.add(t);
            jobSemaphore.release();
        }
    }

    private class ReadCallback implements ClientCallback<List<BulletinBoardMessage>>{

        private List<BulletinBoardMessage> expectedMsgList;

        public ReadCallback(List<BulletinBoardMessage> expectedMsgList) {
            this.expectedMsgList = expectedMsgList;
        }

        @Override
        public void handleCallback(List<BulletinBoardMessage> messages) {

            System.err.println(messages);
            jobSemaphore.release();

            BulletinBoardMessageComparator msgComparator = new BulletinBoardMessageComparator();

            assertThat(messages.size(), is(expectedMsgList.size()));

            Iterator<BulletinBoardMessage> expectedMessageIterator = expectedMsgList.iterator();
            Iterator<BulletinBoardMessage> receivedMessageIterator = messages.iterator();

            while (expectedMessageIterator.hasNext()) {
                assertThat(msgComparator.compare(expectedMessageIterator.next(), receivedMessageIterator.next()), is(0));
            }

        }

        @Override
        public void handleFailure(Throwable t) {
            thrown.add(t);
            jobSemaphore.release();
        }
    }

    private BulletinBoardClient bulletinBoardClient;

    private PostCallback postCallback;
    private RedundancyCallback redundancyCallback;
    private ReadCallback readCallback;

    private static String PROP_GETTY_URL = "gretty.httpBaseURI";
    private static String DEFAULT_BASE_URL = "http://localhost:8081";
    private static String BASE_URL =  System.getProperty(PROP_GETTY_URL, DEFAULT_BASE_URL);

    @Before
    public void init(){

        bulletinBoardClient = new ThreadedBulletinBoardClient();

        List<String> testDB = new LinkedList<String>();
        testDB.add(BASE_URL);

        bulletinBoardClient.init(BulletinBoardClientParams.newBuilder()
            .addBulletinBoardAddress("http://localhost:8081")
            .setMinRedundancy((float) 1.0)
            .build());

        postCallback = new PostCallback();
        redundancyCallback = new RedundancyCallback((float) 1.0);

        thrown = new Vector<>();
        jobSemaphore = new Semaphore(0);

    }

    @Test
    public void postTest() {

        byte[] b1 = {(byte) 1, (byte) 2, (byte) 3, (byte) 4};
        byte[] b2 = {(byte) 11, (byte) 12, (byte) 13, (byte) 14};
        byte[] b3 = {(byte) 21, (byte) 22, (byte) 23, (byte) 24};
        byte[] b4 = {(byte) 4, (byte) 5, (byte) 100, (byte) -50, (byte) 0};

        BulletinBoardMessage msg;

        MessageFilterList filterList;
        List<BulletinBoardMessage> msgList;

        MessageID messageID;

        Comparator<BulletinBoardMessage> msgComparator = new BulletinBoardMessageComparator();

        msg = BulletinBoardMessage.newBuilder()
                .setMsg(UnsignedBulletinBoardMessage.newBuilder()
                        .addTag("Signature")
                        .addTag("Trustee")
                        .setData(ByteString.copyFrom(b1))
                        .build())
                .addSig(Crypto.Signature.newBuilder()
                        .setType(Crypto.SignatureType.DSA)
                        .setData(ByteString.copyFrom(b2))
                        .setSignerId(ByteString.copyFrom(b3))
                        .build())
                .addSig(Crypto.Signature.newBuilder()
                        .setType(Crypto.SignatureType.ECDSA)
                        .setData(ByteString.copyFrom(b3))
                        .setSignerId(ByteString.copyFrom(b2))
                        .build())
                .build();

        messageID = bulletinBoardClient.postMessage(msg,postCallback);

        try {
            jobSemaphore.acquire();
        } catch (InterruptedException e) {
            System.err.println(e.getCause() + " " + e.getMessage());
        }

        bulletinBoardClient.getRedundancy(messageID,redundancyCallback);

        filterList = MessageFilterList.newBuilder()
                .addFilter(
                        MessageFilter.newBuilder()
                                .setType(FilterType.TAG)
                                .setTag("Signature")
                                .build()
                )
                .addFilter(
                        MessageFilter.newBuilder()
                                .setType(FilterType.TAG)
                                .setTag("Trustee")
                                .build()
                )
                .build();

        msgList = new LinkedList<BulletinBoardMessage>();
        msgList.add(msg);

        readCallback = new ReadCallback(msgList);

        bulletinBoardClient.readMessages(filterList, readCallback);
        try {
            jobSemaphore.acquire(2);
        } catch (InterruptedException e) {
            System.err.println(e.getCause() + " " + e.getMessage());
        }

        bulletinBoardClient.close();

        for (Throwable t : thrown) {
            System.err.println(t.getMessage());
        }
        if (thrown.size() > 0) {
            assert false;
        }

    }

}