/*
 This file is part of GNUnet.
  (C) 2012, 2013 Christian Grothoff (and other contributing authors)

  GNUnet is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 3, or (at your
  option) any later version.

  GNUnet is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with GNUnet; see the file COPYING.  If not, write to the
  Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  Boston, MA 02111-1307, USA.
 */

package org.gnunet.consensus;

import org.gnunet.testbed.*;
import org.gnunet.testing.TestingFixture;
import org.gnunet.util.*;
import org.junit.Assert;
import org.junit.Test;

import java.util.HashSet;

/**
 * Test for consensus with multiple peers using testbed.
 */
public class ConsensusTestbedTest extends TestingFixture {
    @Test
    public void test_consensus_testbed_2peers() {
        final Wrapper<Boolean> done = new Wrapper<Boolean>(false);
        new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer[] peers = new Controller.Peer[2];
            Consensus[] consensi = new Consensus[2];
            PeerIdentity[] identities = new PeerIdentity[2];
            @SuppressWarnings("unchecked")
            HashSet<String>[] received = new HashSet[]{new HashSet(), new HashSet()};
            int peersCreated;
            int peersInfoDone;
            int peersStarted;
            int peersConcludeDone;
            HashCode sessionId = HashCode.random();
            AbsoluteTime start;
            AbsoluteTime deadline;

            class MyConsensusCallback implements ConsensusCallback {
                int n;
                public MyConsensusCallback(int n) {
                    this.n = n;
                }
                @Override
                public void onElement(ConsensusElement element) {
                    received[n].add(new String(element.data));
                }

                @Override
                public void onDone() {
                    System.out.println("peer " + n + " conclude done");
                    peersConcludeDone++;
                    if (peersConcludeDone == 2) {
                        Assert.assertEquals(4, received[0].size());
                        Assert.assertEquals(4, received[1].size());

                        done.set(true);
                        consensi[0].destroy();
                        consensi[1].destroy();
                        c.disconnect();
                        cp.stop();
                    }
                }
            }

            class ConsensusAdapter implements ServiceAdapter {
                int n;
                public ConsensusAdapter(int n) {
                    this.n = n;
                }
                @Override
                public void onConnect(Configuration cfg) {
                    System.out.println("connecting to consensus");
                    consensi[n] = new Consensus(cfg, identities, sessionId, start, deadline);
                    consensi[n].insertElement(new ConsensusElement("foo".getBytes(), 0));
                    consensi[n].insertElement(new ConsensusElement("bar".getBytes(), 0));
                    consensi[n].insertElement(new ConsensusElement(("num" + n).getBytes(), 0));
                    consensi[n].conclude(new MyConsensusCallback(n));
                }

                @Override
                public void onDisconnect() {
                    // fixme: why??
                    consensi[n].destroy();
                    consensi[n] = null;
                }
            }

            class ConnectedHandler implements OperationCompletionCallback {
                @Override
                public void onCompletion() {
                    peers[0].getServiceConnection("consensus", new ConsensusAdapter(0));
                    peers[1].getServiceConnection("consensus", new ConsensusAdapter(1));
                }

                @Override
                public void onError(String emsg) {
                    Assert.fail(emsg);
                }
            }

            class InfoCallback implements PeerInformationCallback {
                int n;
                public InfoCallback(int n) {
                    this.n = n;
                }
                @Override
                public void onSuccess(PeerIdentity peerIdentity, Configuration configuration) {
                    peersInfoDone++;
                    identities[n] = peerIdentity;
                    if (peersInfoDone == 2) {
                        peers[0].connectOverlay(peers[1], new ConnectedHandler());
                    }
                }
            }

            class ChurnStart implements PeerChurnCallback {
                @Override
                public void onChurnSuccess() {
                    peersStarted++;
                    if (peersStarted == 2) {
                        peers[0].requestInformation(new InfoCallback(0));
                        peers[1].requestInformation(new InfoCallback(1));
                    }
                }

                @Override
                public void onChurnError(String emsg) {
                    Assert.fail(emsg);
                }
            }

            class PCB implements PeerCreateCallback {
                int n;
                public PCB(int n) {
                    this.n = n;
                }
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    System.out.println("peer created!");
                    peers[n] = peer;
                    peersCreated++;
                    if (peersCreated == 2) {
                        peers[0].start(new ChurnStart());
                        peers[1].start(new ChurnStart());
                    }
                }

                @Override
                public void onError(String errorMessage) {
                    Assert.fail();
                }
            }

            @Override
            public void run() {
                start = AbsoluteTime.now();
                deadline = start.add(RelativeTime.SECOND.multiply(10));
                // use local peer's config, does that really make sense?
                h = new Host(null, null, getConfiguration(), 0);
                cp = new ControllerProc();
                cp.start("127.0.0.1", h, new ControllerStatusCallback() {
                    @Override
                    public void onStartupSuccess(Configuration cfg) {
                        System.out.println("startup success");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        Configuration peerCfg = getConfiguration().clone();
                        peerCfg.setValueString("arm", "DEFAULTSERVICES", "set consensus");
                        c.createPeer(h, peerCfg, new PCB(0));
                        c.createPeer(h, peerCfg, new PCB(1));
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");

        Assert.assertTrue(done.get());

    }
}
