/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2007 University of Washington
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/log.h"
#include "ns3/enum.h"
#include "ns3/uinteger.h"
#include "ns3/double.h"
#include "ns3/nampt-l4-protocol.h"
#include "drop-tail-queue.h"


NS_LOG_COMPONENT_DEFINE ("DropTailQueue");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (DropTailQueue);

TypeId DropTailQueue::GetTypeId (void) 
{
  static TypeId tid = TypeId ("ns3::DropTailQueue")
    .SetParent<Queue> ()
    .AddConstructor<DropTailQueue> ()
    .AddAttribute ("Mode", 
                   "Whether to use bytes (see MaxBytes) or packets (see MaxPackets) as the maximum queue size metric.",
                   EnumValue (QUEUE_MODE_PACKETS),
                   MakeEnumAccessor (&DropTailQueue::SetMode),
                   MakeEnumChecker (QUEUE_MODE_BYTES, "QUEUE_MODE_BYTES",
                                    QUEUE_MODE_PACKETS, "QUEUE_MODE_PACKETS"))
    .AddAttribute ("MaxPackets", 
                   "The maximum number of packets accepted by this DropTailQueue.",
                   UintegerValue (100),
                   MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("MaxBytes", 
                   "The maximum number of bytes accepted by this DropTailQueue.",
                   UintegerValue (100 * 65535),
                   MakeUintegerAccessor (&DropTailQueue::m_maxBytes),
                   MakeUintegerChecker<uint32_t> ())
	.AddAttribute ("MarkLine", 
				 "Use ECN to mark every packets if the queue length is larger than MarkLine.",
				 UintegerValue (0),
				 MakeUintegerAccessor (&DropTailQueue::m_markLine),
				 MakeUintegerChecker<uint32_t> ())
  ;

  return tid;
}

DropTailQueue::DropTailQueue () :
  Queue (),
  m_packets (),
  m_bytesInQueue (0)
{
  NS_LOG_FUNCTION_NOARGS ();
  m_inputBytes = 0;
  m_inputPkts = 0;
  m_dropPkts = 0;
  m_markPkts = 0;
}

DropTailQueue::~DropTailQueue ()
{
  NS_LOG_FUNCTION_NOARGS ();
}

void
DropTailQueue::SetMode (DropTailQueue::QueueMode mode)
{
  NS_LOG_FUNCTION (mode);
  m_mode = mode;
}

DropTailQueue::QueueMode
DropTailQueue::GetMode (void)
{
  NS_LOG_FUNCTION_NOARGS ();
  return m_mode;
}

void
DropTailQueue::InterruptProcess(void)
{
  static const uint64_t capacity = m_bps * (-m_sample) / 8000.0; // bytes
  
  m_sample = -m_sample;
  m_event = Simulator::Schedule(MilliSeconds(m_sample),
            &DropTailQueue::InterruptProcess, this);

  double inputRate = m_inputBytes * 0.008 / m_sample; // Mbps
  m_inputRate = m_inputRate*0.9 + inputRate*0.1;
  //double diffRate = inputRate - m_bps / 1000000.0;
  //int q = diffRate * m_sample / 0.008; //  bytes
  uint32_t averageQueue = 0;
  if (m_inputBytes > capacity)
  	averageQueue = m_inputBytes - capacity;

  NAMPT_LOG2("QUEUE " << m_qname << " " << 
			inputRate * 1000000.0 / m_bps << " " << // utilization
			averageQueue << " " << // bytes
			(double)m_markPkts / (double)m_inputPkts << " " << 
			(double)m_dropPkts / (double)m_inputPkts);
	  
  // for next process
  m_inputBytes = 0;
  m_sample = -m_sample;
}

bool 
DropTailQueue::DoEnqueue (Ptr<Packet> p)
{
  NS_LOG_FUNCTION (this << p);
  uint32_t nPackets = m_packets.size ();
  uint32_t pSize = p->GetSize ();
  uint32_t flags;

  // statistics
  m_inputBytes += pSize;
  m_inputPkts++;

  // cleared by ~Queue()
  if (m_sample > 0 && !m_event.IsRunning() )
  {
    m_event = Simulator::Schedule(MilliSeconds(m_sample),
              &DropTailQueue::InterruptProcess, this);
    m_sample = -m_sample;
  }
  
  if (m_mode == QUEUE_MODE_PACKETS && (nPackets >= m_maxPackets))
    {
      NS_LOG_LOGIC ("Queue full (at max packets) -- droppping pkt");
	  goto DropTailQueue_Drop;
    }

  if (m_mode == QUEUE_MODE_BYTES && (m_bytesInQueue + pSize >= m_maxBytes))
    {
      NS_LOG_LOGIC ("Queue full (packet would exceed max bytes) -- droppping pkt");
      goto DropTailQueue_Drop;
    }
  
  // ecn support
  //if (m_dev)
  {
    uint32_t param = 0;
    if (m_markLine > 0 && (nPackets >= m_markLine)) 
    {
      m_dev->ProcessPacket(p, 1, &param); // dctcp marking + explicit control
      m_markPkts++;
    }
    else               // explicit control
    {
      m_dev->ProcessPacket(p, 2, &param);
    }
  }
  
DropTailQueue_Enqueue:
  m_bytesInQueue += pSize;
  m_packets.push (p);

  NS_LOG_LOGIC ("Number packets " << ++nPackets);
  NS_LOG_LOGIC ("Number bytes " << m_bytesInQueue);
  return true;
  
DropTailQueue_Drop:
  m_dev->ProcessPacket(p, 3, &flags);
  if (flags == 0)
    Drop (p);
  else
  	goto DropTailQueue_Enqueue;

  m_dropPkts++;
  return false;
}

Ptr<Packet>
DropTailQueue::DoDequeue (void)
{
  NS_LOG_FUNCTION (this);

  if (m_packets.empty ())
    {
      NS_LOG_LOGIC ("Queue empty");
      return 0;
    }

  Ptr<Packet> p = m_packets.front ();
  m_packets.pop ();
  m_bytesInQueue -= p->GetSize ();

  NS_LOG_LOGIC ("Popped " << p);

  NS_LOG_LOGIC ("Number packets " << m_packets.size ());
  NS_LOG_LOGIC ("Number bytes " << m_bytesInQueue);

  return p;
}

Ptr<const Packet>
DropTailQueue::DoPeek (void) const
{
  NS_LOG_FUNCTION (this);

  if (m_packets.empty ())
    {
      NS_LOG_LOGIC ("Queue empty");
      return 0;
    }

  Ptr<Packet> p = m_packets.front ();

  NS_LOG_LOGIC ("Number packets " << m_packets.size ());
  NS_LOG_LOGIC ("Number bytes " << m_bytesInQueue);

  return p;
}

} // namespace ns3

