/*
 * Copyright (C) 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jboss.errai.ioc.tests.lifecycle.client.local;

import org.jboss.errai.ioc.client.container.IOC;
import org.jboss.errai.ioc.client.container.SyncBeanManager;
import org.jboss.errai.ioc.client.lifecycle.api.Access;
import org.jboss.errai.ioc.client.lifecycle.api.Destruction;
import org.jboss.errai.ioc.client.lifecycle.api.LifecycleCallback;
import org.jboss.errai.ioc.client.lifecycle.api.LifecycleEvent;
import org.jboss.errai.ioc.client.lifecycle.api.LifecycleListener;
import org.jboss.errai.ioc.client.lifecycle.api.LifecycleListenerGenerator;
import org.jboss.errai.ioc.client.test.AbstractErraiIOCTest;

public class IOCLifecycleTest extends AbstractErraiIOCTest {

  public static class Counter {
    private int counter = 0;

    public void add(final int number) {
      counter += number;
    }

    public int getValue() {
      return counter;
    }
  }

  private static class CountingListener implements LifecycleListener<Integer> {
    private final Counter listenerCounter;

    private CountingListener(final Counter counter) {
      listenerCounter = counter;
    }

    @Override
    public void observeEvent(LifecycleEvent<Integer> event) {
      listenerCounter.add(1);
    }

    @Override
    public boolean isObserveableEventType(Class<? extends LifecycleEvent<Integer>> eventType) {
      return eventType.equals(Access.class);
    }
  }

  @SuppressWarnings("unchecked")
  public void testSingleLifecycleListenerIsCalled() {
    // Build listener and generator
    final Counter listenerCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);
    final LifecycleListenerGenerator<Integer> generator = new LifecycleListenerGenerator<Integer>() {
      @Override
      public LifecycleListener<Integer> newInstance() {
        return listener;
      }
    };

    // Build event
    final Integer instance = 1337;
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    // Register listener
    IOC.registerLifecycleListener(Integer.class, generator);

    // Precondition
    assertEquals(0, listenerCounter.getValue());

    final Counter callbackCounter = new Counter();

    event.fireAsync(instance, new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    });

    assertEquals(1, listenerCounter.getValue());
    assertEquals(1, callbackCounter.getValue());
  }

  @SuppressWarnings("unchecked")
  public void testLifecycleListenerIsUnsubscribedSameInstance() throws Exception {
    // Build listener and generator
    final Counter listenerCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);
    final LifecycleListenerGenerator<Integer> generator = new LifecycleListenerGenerator<Integer>() {
      @Override
      public LifecycleListener<Integer> newInstance() {
        return listener;
      }
    };

    // Build event
    final Integer instance = 1337;
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    // Register listener
    IOC.registerLifecycleListener(Integer.class, generator);

    // Precondition
    assertEquals(0, listenerCounter.getValue());

    final Counter callbackCounter = new Counter();
    final LifecycleCallback callback = new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    };
    event.fireAsync(instance, callback);

    // Still precondition
    assertEquals(1, listenerCounter.getValue());
    assertEquals(1, callbackCounter.getValue());

    // Unregister
    IOC.unregisterLifecycleListener(Integer.class, generator);

    event.fireAsync(instance, callback);

    assertEquals(1, listenerCounter.getValue());
  }

  @SuppressWarnings("unchecked")
  public void testLifecycleListenerIsUnsubscribedNewInstance() throws Exception {
    // Build listener and generator
    final Counter listenerCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);
    final LifecycleListenerGenerator<Integer> generator = new LifecycleListenerGenerator<Integer>() {
      @Override
      public LifecycleListener<Integer> newInstance() {
        return listener;
      }
    };

    // Build event
    final Integer instance = 1337;
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    // Register listener
    IOC.registerLifecycleListener(Integer.class, generator);

    // Precondition
    assertEquals(0, listenerCounter.getValue());

    final Counter callbackCounter = new Counter();
    final LifecycleCallback callback = new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    };
    event.fireAsync(instance, callback);

    // Still precondition
    assertEquals(1, listenerCounter.getValue());
    assertEquals(1, callbackCounter.getValue());

    // Unregister
    IOC.unregisterLifecycleListener(Integer.class, generator);

    final Integer newInstance = 649;
    event.fireAsync(newInstance, callback);

    assertEquals(1, listenerCounter.getValue());
  }

  @SuppressWarnings("unchecked")
  public void testListenersNotSharedAcrossInstances() throws Exception {
    // Build listeners and generator
    final Counter firstCounter = new Counter();
    final LifecycleListener<Integer> firstListener = new CountingListener(firstCounter);
    final Counter secondCounter = new Counter();
    final LifecycleListener<Integer> secondListener = new CountingListener(secondCounter);
    final LifecycleListenerGenerator<Integer> generator = new LifecycleListenerGenerator<Integer>() {
      private int generated = 0;

      @Override
      public LifecycleListener<Integer> newInstance() {
        generated += 1;
        if (generated == 1)
          return firstListener;
        else if (generated == 2)
          return secondListener;
        else
          throw new IllegalStateException("Test should only call this generator twice.");
      }
    };

    // Build event and instances
    final Integer firstInstance = 1337;
    final Integer secondInstance = 649;
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    final Counter callbackCounter = new Counter();
    final LifecycleCallback callback = new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    };
    
    // Register generator
    IOC.registerLifecycleListener(Integer.class, generator);
    
    // Precondition
    assertEquals(0, firstCounter.getValue());
    assertEquals(0, secondCounter.getValue());
    assertEquals(0, callbackCounter.getValue());

    // Fire event on first instance.
    event.fireAsync(firstInstance, callback);
    
    assertEquals(1, callbackCounter.getValue());
    assertEquals(1, firstCounter.getValue());
    assertEquals(0, secondCounter.getValue());
    
    // Fire event on second instance.
    event.fireAsync(secondInstance, callback);

    assertEquals(2, callbackCounter.getValue());
    assertEquals(1, firstCounter.getValue());
    assertEquals(1, secondCounter.getValue());
  }
  
  @SuppressWarnings("unchecked")
  public void testRegisterSingleInstanceListener() throws Exception {
    final Counter listenerCounter = new Counter();
    final Counter callbackCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);
    
    final Integer instance = 1337;
    
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();
    final LifecycleCallback callback = new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    };
    
    IOC.registerLifecycleListener(instance, listener);
    
    // Precondition
    assertEquals(0, callbackCounter.getValue());
    assertEquals(0, listenerCounter.getValue());
    
    event.fireAsync(instance, callback);
    
    assertEquals(1, callbackCounter.getValue());
    assertEquals(1, listenerCounter.getValue());
  }
  
  @SuppressWarnings("unchecked")
  public void testUnregisterSingleInstanceListener() throws Exception {
    final Counter listenerCounter = new Counter();
    final Counter callbackCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);
    
    final Integer instance = 1337;
    
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();
    final LifecycleCallback callback = new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertTrue(success);
        callbackCounter.add(1);
      }
    };
    
    IOC.registerLifecycleListener(instance, listener);
    
    // Precondition
    assertEquals(0, callbackCounter.getValue());
    assertEquals(0, listenerCounter.getValue());
    
    event.fireAsync(instance, callback);
    
    // Still precondition
    assertEquals(1, callbackCounter.getValue());
    assertEquals(1, listenerCounter.getValue());
    
    // Actual test
    IOC.unregisterLifecycleListener(instance, listener);
    
    event.fireAsync(instance, callback);
    assertEquals(2, callbackCounter.getValue());
    assertEquals(1, listenerCounter.getValue());
  }

  @SuppressWarnings("unchecked")
  public void testUnregisterSingleInstanceListenerTwice() throws Exception {
    final Counter listenerCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);

    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    final Integer instance = 1337;
    IOC.registerLifecycleListener(instance, listener);

    // Precondition
    assertEquals(0, listenerCounter.getValue());
    event.fireAsync(instance);
    assertEquals(1, listenerCounter.getValue());
    IOC.unregisterLifecycleListener(instance, listener);
    event.fireAsync(instance);
    assertEquals(1, listenerCounter.getValue());

    // Actual test
    try {
      IOC.unregisterLifecycleListener(instance, listener);
    }
    catch (Exception e) {
      fail("Second unregister call caused an exception: " + e.getMessage());
    }
  }

  @SuppressWarnings("unchecked")
  public void testUnregisterSingleInstanceListenerAfterDestruction() throws Exception {
    final Counter listenerCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter);

    final SyncBeanManager bm = IOC.getBeanManager();
    final Access<Integer> event = bm.lookupBean(Access.class).getInstance();
    final Destruction<Integer> destruction = bm.lookupBean(Destruction.class).getInstance();

    final Integer instance = 1337;
    IOC.registerLifecycleListener(instance, listener);

    // Precondition
    assertEquals(0, listenerCounter.getValue());
    event.fireAsync(instance);
    assertEquals(1, listenerCounter.getValue());
    destruction.fireAsync(instance);
    event.fireAsync(instance);
    assertEquals(1, listenerCounter.getValue());

    // Actual test
    try {
      IOC.unregisterLifecycleListener(instance, listener);
    }
    catch (Exception e) {
      fail("Unregister call caused an exception: " + e.getMessage());
    }
  }

  @SuppressWarnings("unchecked")
  public void testVeto() throws Exception {
    final Counter listenerCounter = new Counter();
    final Counter callbackCounter = new Counter();
    final LifecycleListener<Integer> listener = new CountingListener(listenerCounter) {
      @Override
      public void observeEvent(final LifecycleEvent<Integer> event) {
        super.observeEvent(event);
        event.veto();
      }
    };
    IOC.registerLifecycleListener(Integer.class, new LifecycleListenerGenerator<Integer>() {

      @Override
      public LifecycleListener<Integer> newInstance() {
        return listener;
      }
    });
    
    final Integer instance = 1337;
    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    assertEquals(0, callbackCounter.getValue());
    assertEquals(0, listenerCounter.getValue());

    event.fireAsync(instance, new LifecycleCallback() {
      @Override
      public void callback(boolean success) {
        assertFalse(success);
        callbackCounter.add(1);
      }
    });
    
    assertEquals(1, callbackCounter.getValue());
    assertEquals(1, listenerCounter.getValue());
  }

  @SuppressWarnings("unchecked")
  public void testGetInstanceReturnsNullOutsideOfFireAsync() throws Exception {
    final Integer instance = 1337;
    final LifecycleListener<Integer> listener = new LifecycleListener<Integer>() {
      @Override
      public void observeEvent(final LifecycleEvent<Integer> event) {
        assertEquals(instance, event.getInstance());
      }
      @Override
      public boolean isObserveableEventType(Class<? extends LifecycleEvent<Integer>> eventType) {
        return true;
      }
    };
    IOC.registerLifecycleListener(instance, listener);

    final Access<Integer> event = IOC.getBeanManager().lookupBean(Access.class).getInstance();

    assertNull(event.getInstance());
    event.fireAsync(instance, new LifecycleCallback() {
      @Override
      public void callback(final boolean success) {
        assertNull(event.getInstance());
      }
    });
    assertNull(event.getInstance());
  }

  @Override
  public String getModuleName() {
    return "org.jboss.errai.ioc.tests.lifecycle.LifecycleTests";
  }

}
