/*
   Copyright (c) 2012 LinkedIn Corp.

   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 com.linkedin.d2.discovery.event;

import static com.linkedin.d2.discovery.util.LogUtil.debug;
import static com.linkedin.d2.discovery.util.LogUtil.error;
import static com.linkedin.d2.discovery.util.LogUtil.info;
import static com.linkedin.d2.discovery.util.LogUtil.warn;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PropertyEventThread extends Thread
{
  private static final Logger             _log =
                                                LoggerFactory.getLogger(PropertyEventThread.class);

  private BlockingQueue<PropertyEvent> _messages;

  public PropertyEventThread(String name)
  {
    this(name, Integer.MAX_VALUE);
  }

  public PropertyEventThread(String name, int size)
  {
    this(name, size, false);
  }

  public PropertyEventThread(String name, int size, boolean start)
  {
    _messages = new LinkedBlockingQueue<>(size);

    setDaemon(true);
    setName("PropertyEventThread-" + getId() + "-" + name);

    if (start)
    {
      start();
    }
  }

  public int getRemainingCapacity()
  {
    return _messages.remainingCapacity();
  }

  public int getQueuedMessageCount()
  {
    return _messages.size();
  }

  @Override
  public void start()
  {
    info(_log, "starting thread: ", getName());

    super.start();
  }

  public boolean send(PropertyEvent message)
  {
    if (message == null)
    {
      warn(_log, "send called with null message");
    }
    else if (!isAlive())
    {
      error(_log, "sending message while thread ", getName(), " is down: ", message);
    }
    else
    {
      debug(_log, getName(), " got message: ", message);

      return _messages.add(message);
    }

    int remainingCapacity = getRemainingCapacity();

    if (remainingCapacity < 1000)
    {
      warn(_log,
           "remaining capacity for thread ",
           getName(),
           " is dangerously low: ",
           remainingCapacity);
    }

    return false;

  }

  @Override
  public void run()
  {
    boolean shutdown = false;

    while (!shutdown || _messages.size() > 0)
    {
      try
      {
        PropertyEvent message = _messages.poll(1, TimeUnit.SECONDS);

        if (message != null)
        {
          message.run();
        }
        else
        {
          debug(_log, "received null message in thread: ", getName());
        }
      }
      catch (InterruptedException e)
      {
        shutdown = true;

        info(_log, "thread ", getName(), " got interrupt, so shutting down");
      }
      catch (Exception e)
      {
        _log.error("Unhandled exception in event thread", e);
      }
    }

    info(_log, "thread ", getName(), " finished run() and is now dying");
  }

  // interfaces
  public static abstract class PropertyEvent implements Runnable
  {

    private String _description;

    public PropertyEvent(String description)
    {
      _description = description;
    }

    @Override
    public String toString()
    {
      return "PropertyEvent [_description=" + _description + "]";
    }

    @Override
    final public void run()
    {
      try
      {
        innerRun();
      }
      catch (RuntimeException e)
      {
        _log.error("Received exception from: " + _description, e);
      }
    }

    abstract public void innerRun();
  }

  public static interface PropertyEventShutdownCallback
  {
    void done();
  }
}
