/*
 * JORAM: Java(TM) Open Reliable Asynchronous Messaging
 * Copyright (C) 2022 ScalAgent Distributed Technologies
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 *
 * Initial developer(s): ScalAgent Distributed Technologies
 * Contributor(s):
 */
package fr.dyade.aaa.agent;

import org.junit.Assert;
import org.junit.Test;

import fr.dyade.aaa.common.EmptyQueueException;

/**
 * Tests the initialization of the Engine.
 * More specifically tests the order of messages in the queue during initialization.
 */
public class EngineInitTest {
  
  /**
   * Engine simulation.
   * Be careful, insert, validate and isPrior methods needs to be identical to real Engine.
   */
  class EngineComparator implements MessageComparator {
    int stamp;
    MessageQueue qin;
    
    public MessageQueue getQueue() {
      return qin;
    }
    
    EngineComparator(int stamp) {
      this.stamp = stamp;
      qin = new MessageVector("test", false);
    }
    
    public void insert(Message msg) {
      qin.insert(msg, this);
    }

    public void validate() {
      qin.validate();
    }
    
    public boolean isPrior(Message m1, Message m2) {
      if (((m1.getStamp() <= stamp) && (m2.getStamp() <= stamp)) ||
          ((m1.getStamp() > stamp) && (m2.getStamp() > stamp))) {
        // The 2 messages were created in the same stamp generation, check the relative order.
        return (m1.getStamp() < m2.getStamp());
      } else if ((m1.getStamp() > stamp) && (m2.getStamp() <= stamp)) {
        // The first message was created after the buffer was reset.
        // The second to insert was created before the buffer was reset, it is older then insert it.
        return true;
      }
      return false;
    }
  }
  
  /**
   * Simple test with message insertion in bad order.
   */
  @Test
  public void test1() throws Exception {
    EngineComparator engine = new EngineComparator(10);
    
    Message msg = Message.alloc();
    msg.stamp = 6;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 4;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 5;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 10;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 9;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 8;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 7;
    engine.insert(msg);
    
    engine.validate();
    MessageQueue qin = engine .getQueue();
    
    int expected = 4;
    while (expected < 11) {
      msg = qin.pop();
      Assert.assertTrue("Expected=" + expected + ", Received=" + msg.stamp, msg.stamp == expected);
      expected += 1;
    }
    
    try {
      msg = qin.pop();
      Assert.assertTrue("Queue should be empty: " + msg, false);
    } catch (Throwable exc) {
      Assert.assertTrue("Queue should be empty: " + msg, exc instanceof EmptyQueueException);
    }
  }
  
  /**
   * Test with stamp reinitialization.
   */
  @Test
  public void test2() throws Exception {
    EngineComparator engine = new EngineComparator(10);
    
    Message msg = Message.alloc();
    msg.stamp = 3;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 1;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE-2;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 5;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE-3;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 4;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 2;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE-1;
    engine.insert(msg);
    
    engine.validate();
    MessageQueue qin = engine .getQueue();
    
    int[] expected = new int[] { Integer.MAX_VALUE-3, Integer.MAX_VALUE-2, Integer.MAX_VALUE-1, Integer.MAX_VALUE, 1, 2, 3, 4, 5 };
    int nb = 0;
    while (nb < 9) {
      msg = qin.pop();
      Assert.assertTrue("Expected=" + expected[nb] + ", Received=" + msg.stamp, msg.stamp == expected[nb]);
      nb += 1;
    }
    
    try {
      msg = qin.pop();
      Assert.assertTrue("Queue should be empty: " + msg, false);
    } catch (Throwable exc) {
      Assert.assertTrue("Queue should be empty: " + msg, exc instanceof EmptyQueueException);
    }
  }
  
  /**
   * Test with stamp reinitialization.
   */
  @Test
  public void test3() throws Exception {
    EngineComparator engine = new EngineComparator(10);
    
    Message msg = Message.alloc();
    msg.stamp = 3;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE-2;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 0;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = 4;
    engine.insert(msg);
    
    msg = Message.alloc();
    msg.stamp = Integer.MAX_VALUE-1;
    engine.insert(msg);
    
    engine.validate();
    MessageQueue qin = engine .getQueue();
    
    int[] expected = new int[] { Integer.MAX_VALUE-2, Integer.MAX_VALUE-1, 0, 3, 4 };
    int nb = 0;
    while (nb < 5) {
      msg = qin.pop();
      Assert.assertTrue("Expected=" + expected[nb] + ", Received=" + msg.stamp, msg.stamp == expected[nb]);
      nb += 1;
    }
    
    try {
      msg = qin.pop();
      Assert.assertTrue("Queue should be empty: " + msg, false);
    } catch (Throwable exc) {
      Assert.assertTrue("Queue should be empty: " + msg, exc instanceof EmptyQueueException);
    }
  }
}
