// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/tools/flip_server/output_ordering.h"

#include "net/tools/flip_server/flip_config.h"
#include "net/tools/flip_server/sm_connection.h"


namespace net {

// static
double OutputOrdering::server_think_time_in_s_ = 0.0;

OutputOrdering::OutputOrdering(SMConnectionInterface* connection)
    : first_data_senders_threshold_(kInitialDataSendersThreshold),
      connection_(connection) {
  if (connection)
    epoll_server_ = connection->epoll_server();
}

OutputOrdering::~OutputOrdering() {}

void OutputOrdering::Reset() {
  while (!stream_ids_.empty()) {
    StreamIdToPriorityMap::iterator sitpmi = stream_ids_.begin();
    PriorityMapPointer& pmp = sitpmi->second;
    if (pmp.alarm_enabled) {
      epoll_server_->UnregisterAlarm(pmp.alarm_token);
    }
    stream_ids_.erase(sitpmi);
  }
  priority_map_.clear();
  first_data_senders_.clear();
}

bool OutputOrdering::ExistsInPriorityMaps(uint32 stream_id) {
  StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
  return sitpmi != stream_ids_.end();
}

OutputOrdering::BeginOutputtingAlarm::BeginOutputtingAlarm(
    OutputOrdering* oo,
    OutputOrdering::PriorityMapPointer* pmp,
    const MemCacheIter& mci)
    : output_ordering_(oo),
      pmp_(pmp),
      mci_(mci),
      epoll_server_(NULL) {
}

OutputOrdering::BeginOutputtingAlarm::~BeginOutputtingAlarm() {
  if (epoll_server_ && pmp_->alarm_enabled)
    epoll_server_->UnregisterAlarm(pmp_->alarm_token);
}

int64 OutputOrdering::BeginOutputtingAlarm::OnAlarm() {
  OnUnregistration();
  output_ordering_->MoveToActive(pmp_, mci_);
  VLOG(2) << "ON ALARM! Should now start to output...";
  delete this;
  return 0;
}

void OutputOrdering::BeginOutputtingAlarm::OnRegistration(
    const EpollServer::AlarmRegToken& tok,
    EpollServer* eps) {
  epoll_server_ = eps;
  pmp_->alarm_token = tok;
  pmp_->alarm_enabled = true;
}

void OutputOrdering::BeginOutputtingAlarm::OnUnregistration() {
  pmp_->alarm_enabled = false;
}

void OutputOrdering::BeginOutputtingAlarm::OnShutdown(EpollServer* eps) {
  OnUnregistration();
}

void OutputOrdering::MoveToActive(PriorityMapPointer* pmp, MemCacheIter mci) {
  VLOG(2) << "Moving to active!";
  first_data_senders_.push_back(mci);
  pmp->ring = &first_data_senders_;
  pmp->it = first_data_senders_.end();
  --pmp->it;
  connection_->ReadyToSend();
}

void OutputOrdering::AddToOutputOrder(const MemCacheIter& mci) {
  if (ExistsInPriorityMaps(mci.stream_id))
    LOG(ERROR) << "OOps, already was inserted here?!";

  double think_time_in_s = server_think_time_in_s_;
  std::string x_server_latency =
    mci.file_data->headers->GetHeader("X-Server-Latency").as_string();
  if (!x_server_latency.empty()) {
    char* endp;
    double tmp_think_time_in_s = strtod(x_server_latency.c_str(), &endp);
    if (endp != x_server_latency.c_str() + x_server_latency.size()) {
      LOG(ERROR) << "Unable to understand X-Server-Latency of: "
                 << x_server_latency << " for resource: "
                 <<  mci.file_data->filename.c_str();
    } else {
      think_time_in_s = tmp_think_time_in_s;
    }
  }
  StreamIdToPriorityMap::iterator sitpmi;
  sitpmi = stream_ids_.insert(
      std::pair<uint32, PriorityMapPointer>(mci.stream_id,
                                            PriorityMapPointer())).first;
  PriorityMapPointer& pmp = sitpmi->second;

  BeginOutputtingAlarm* boa = new BeginOutputtingAlarm(this, &pmp, mci);
  VLOG(1) << "Server think time: " << think_time_in_s;
  epoll_server_->RegisterAlarmApproximateDelta(
      think_time_in_s * 1000000, boa);
}

void OutputOrdering::SpliceToPriorityRing(PriorityRing::iterator pri) {
  MemCacheIter& mci = *pri;
  PriorityMap::iterator pmi = priority_map_.find(mci.priority);
  if (pmi == priority_map_.end()) {
    pmi = priority_map_.insert(
        std::pair<uint32, PriorityRing>(mci.priority, PriorityRing())).first;
  }

  pmi->second.splice(pmi->second.end(),
                     first_data_senders_,
                     pri);
  StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(mci.stream_id);
  sitpmi->second.ring = &(pmi->second);
}

MemCacheIter* OutputOrdering::GetIter() {
  while (!first_data_senders_.empty()) {
    MemCacheIter& mci = first_data_senders_.front();
    if (mci.bytes_sent >= first_data_senders_threshold_) {
      SpliceToPriorityRing(first_data_senders_.begin());
    } else {
      first_data_senders_.splice(first_data_senders_.end(),
                                first_data_senders_,
                                first_data_senders_.begin());
      mci.max_segment_size = kInitialDataSendersThreshold;
      return &mci;
    }
  }
  while (!priority_map_.empty()) {
    PriorityRing& first_ring = priority_map_.begin()->second;
    if (first_ring.empty()) {
      priority_map_.erase(priority_map_.begin());
      continue;
    }
    MemCacheIter& mci = first_ring.front();
    first_ring.splice(first_ring.end(),
                      first_ring,
                      first_ring.begin());
    mci.max_segment_size = kSpdySegmentSize;
    return &mci;
  }
  return NULL;
}

void OutputOrdering::RemoveStreamId(uint32 stream_id) {
  StreamIdToPriorityMap::iterator sitpmi = stream_ids_.find(stream_id);
  if (sitpmi == stream_ids_.end())
    return;

  PriorityMapPointer& pmp = sitpmi->second;
  if (pmp.alarm_enabled)
    epoll_server_->UnregisterAlarm(pmp.alarm_token);
  else
    pmp.ring->erase(pmp.it);
  stream_ids_.erase(sitpmi);
}

}  // namespace net