/*
 This file is part of GNUnet.
  (C) 2014 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.testbed;

import org.gnunet.testing.TestingFixture;
import org.gnunet.util.Configuration;
import org.gnunet.util.PeerIdentity;
import org.gnunet.util.Program;
import org.gnunet.util.Wrapper;
import org.junit.Assert;
import org.junit.Test;

public class TestbedTest extends TestingFixture {

    @Test(timeout = 10000)
    public void test_controller_proc() {
        final Wrapper<Boolean> success = new Wrapper<Boolean>(false);
        new Program() {
            @Override
            public void run() {
                // use local peer's config, does that really make sense?
                Host h = new Host(null, null, getConfiguration(), 0);
                final ControllerProc cp = new ControllerProc();
                cp.start("127.0.0.1", h, new ControllerStatusCallback() {
                    @Override
                    public void onStartupSuccess(Configuration cfg) {
                        success.set(true);
                        cp.stop();
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(success.get());
    }


    @Test(timeout = 10000)
    public void test_peer_create() {
        new Program() {
            ControllerProc cp;
            Host h;
            Controller c;

            class PCB implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    System.out.println("peer created!");
                    c.disconnect();
                    cp.stop();
                }

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

            @Override
            public void run() {
                // 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
                        c.createPeer(h, getConfiguration(), new PCB());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
    }

    @Test(timeout = 10000)
    public void test_peer_destroy() {
        new Program() {
            ControllerProc cp;
            Host h;
            Controller c;

            class PCB implements PeerCreateCallback {
                @Override
                public void onPeerCreated(final Controller.Peer peer) {
                    System.out.println("peer created!");
                    peer.destroy(new OperationCompletionCallback() {
                        @Override
                        public void onCompletion() {
                            System.out.println("destroy completed");
                            c.disconnect();
                            cp.stop();
                        }

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

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

            @Override
            public void run() {
                // 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
                        c.createPeer(h, getConfiguration(), new PCB());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
    }

    @Test(timeout = 10000)
    public void test_peer_start() {
        final Wrapper<Boolean> startSuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;

            class MyPeerChurnCallback implements PeerChurnCallback {

                @Override
                public void onChurnSuccess() {
                    System.out.println("peer started");
                    startSuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }

                @Override
                public void onChurnError(String emsg) {
                    Assert.fail("churn failed: " + emsg);
                }
            }

            class MyPeerCreateCallback implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    System.out.println("peer created");
                    peer.start(new MyPeerChurnCallback());
                }

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

            @Override
            public void run() {
                // 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("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(startSuccessful.get());
        Assert.assertEquals(0, ret);
    }

    @Test(timeout = 10000)
    public void test_peer_stop() {
        final Wrapper<Boolean> startSuccessful = new Wrapper<Boolean>(false);
        final Wrapper<Boolean> stopSuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer p;

            class MyPeerStopCallback implements PeerChurnCallback {

                @Override
                public void onChurnSuccess() {
                    stopSuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }

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

            class MyPeerStartCallback implements PeerChurnCallback {

                @Override
                public void onChurnSuccess() {
                    System.out.println("peer started");
                    startSuccessful.set(true);
                    p.stop(new MyPeerStopCallback());
                }

                @Override
                public void onChurnError(String emsg) {
                    Assert.fail("churn failed: " + emsg);
                }
            }

            class MyPeerCreateCallback implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    p = peer;
                    System.out.println("peer created");
                    peer.start(new MyPeerStartCallback());
                }

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

            @Override
            public void run() {
                // 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("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(startSuccessful.get());
        Assert.assertTrue(stopSuccessful.get());
        Assert.assertEquals(0, ret);
    }

    @Test(timeout = 10000)
    public void test_peer_stop_destroy() {
        final Wrapper<Boolean> startSuccessful = new Wrapper<Boolean>(false);
        final Wrapper<Boolean> stopSuccessful = new Wrapper<Boolean>(false);
        final Wrapper<Boolean> destroySuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer p;

            class MyPeerDestroyCallback implements OperationCompletionCallback {

                @Override
                public void onCompletion() {
                    destroySuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }

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

            class MyPeerStopCallback implements PeerChurnCallback {
                @Override
                public void onChurnSuccess() {
                    stopSuccessful.set(true);
                    p.destroy(new MyPeerDestroyCallback());
                }

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

            class MyPeerStartCallback implements PeerChurnCallback {

                @Override
                public void onChurnSuccess() {
                    System.out.println("peer started");
                    startSuccessful.set(true);
                    p.stop(new MyPeerStopCallback());
                }

                @Override
                public void onChurnError(String emsg) {
                    Assert.fail("churn failed: " + emsg);
                }
            }

            class MyPeerCreateCallback implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    p = peer;
                    System.out.println("peer created");
                    peer.start(new MyPeerStartCallback());
                }

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

            @Override
            public void run() {
                // 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("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(startSuccessful.get());
        Assert.assertTrue(stopSuccessful.get());
        Assert.assertTrue(destroySuccessful.get());
        Assert.assertEquals(0, ret);
    }

    @Test(timeout = 5000)
    public void test_peer_get_config() {
        final Wrapper<Boolean> startSuccessful = new Wrapper<Boolean>(false);
        final Wrapper<Boolean> infoSuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer p;

            class MyPeerInformationCallback implements PeerInformationCallback {
                @Override
                public void onSuccess(PeerIdentity peerIdentity, Configuration configuration) {
                    Assert.assertNotNull(peerIdentity);
                    Assert.assertNotNull(configuration);
                    Assert.assertTrue(configuration.getSections().size() > 0);
                    infoSuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }
            }

            class MyPeerCreateCallback implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    p = peer;
                    System.out.println("peer created");
                    startSuccessful.set(true);
                    peer.requestInformation(new MyPeerInformationCallback());
                }

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

            @Override
            public void run() {
                // 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("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback());
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(startSuccessful.get());
        Assert.assertTrue(infoSuccessful.get());
        Assert.assertEquals(0, ret);
    }

    @Test
    public void test_peer_reconfigure() {
        final Wrapper<Boolean> startSuccessful = new Wrapper<Boolean>(false);
        final Wrapper<Boolean> reconfigureSuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer p;

            class MyUpdateConfigDoneCallback implements OperationCompletionCallback {
                @Override
                public void onCompletion() {
                    System.out.println("config update complete");
                    reconfigureSuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }

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

            class MyPeerCreateCallback implements PeerCreateCallback {
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    p = peer;
                    System.out.println("peer created");
                    startSuccessful.set(true);

                    Configuration cfg = new Configuration();
                    cfg.setValueNumber("foo", "bar", 42);
                    cfg.setValueString("my-test-section", "my-test-option", "my-test-value");
                    peer.updateConfiguration(cfg, new MyUpdateConfigDoneCallback());
                }

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

            @Override
            public void run() {
                // use local peer's config, does that really make sense?
                final Configuration configuration = getConfiguration();
                h = new Host(null, null, configuration, 0);
                cp = new ControllerProc();
                cp.start("127.0.0.1", h, new ControllerStatusCallback() {
                    @Override
                    public void onStartupSuccess(Configuration cfg) {
                        System.out.println("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, configuration, new MyPeerCreateCallback());
                    }

                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(reconfigureSuccessful.get());
        Assert.assertEquals(0, ret);
    }

    @Test(timeout = 5000)
    public void test_peer_get_connect_overlay() {
        final Wrapper<Boolean> connectSuccessful = new Wrapper<Boolean>(false);
        int ret = new Program() {
            ControllerProc cp;
            Host h;
            Controller c;
            Controller.Peer p0;
            Controller.Peer p1;
            int peersStarted = 0;

            class MyConnectCompleteCallback implements OperationCompletionCallback {

                @Override
                public void onCompletion() {
                    connectSuccessful.set(true);
                    c.disconnect();
                    cp.stop();
                }

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

            class MyPeerChurnCallback implements PeerChurnCallback {
                int pid;
                MyPeerChurnCallback(int pid) {
                    this.pid = pid;
                }
                @Override
                public void onChurnSuccess() {
                    peersStarted += 1;
                    if (peersStarted == 2) {
                        p0.connectOverlay(p1, new MyConnectCompleteCallback());
                    }

                }

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

            class MyPeerCreateCallback implements PeerCreateCallback {
                int pid;
                MyPeerCreateCallback(int pid) {
                    this.pid = pid;
                }
                @Override
                public void onPeerCreated(Controller.Peer peer) {
                    if (pid == 0) {
                        p0 = peer;
                    } else if (pid == 1) {
                        p1 = peer;
                    } else {
                        Assert.fail();
                    }
                    System.out.println("peer created");
                    peer.start(new MyPeerChurnCallback(pid));
                }

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

            @Override
            public void run() {
                // 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("controller started");
                        c = new Controller(h);
                        // FIXME: use config from resource
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback(0));
                        c.createPeer(h, getConfiguration(), new MyPeerCreateCallback(1));
                    }
                    @Override
                    public void onStartupFailure() {
                        Assert.fail();
                    }
                });
            }
        }.start("-LDEBUG");
        Assert.assertTrue(connectSuccessful.get());
        Assert.assertEquals(0, ret);
    }
}
