/*
 *    Copyright 2006 Baylor University
 * 
 *    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.
 */

#include "ProphetNode.h"
#include <netinet/in.h> // for ntoh*,hton*
#include <math.h> // for pow()

namespace dtn {

ProphetNode::ProphetNode(
        ProphetNodeParams* params,
        const char* logbase)
    : oasys::Logger("ProphetNode",logbase),
      params_(params),
      p_value_(0.0), relay_(false), custody_(false),
      internet_gateway_(false), remote_eid_(EndpointID::NULL_EID())
{
    age_.get_time(); // fill with current time
}

ProphetNode::ProphetNode(const ProphetNode& n)
    : oasys::Logger("ProphetNode",
                    ((oasys::Logger)n).logpath()),
      params_(n.params_),
      p_value_(n.p_value_), relay_(n.relay_), custody_(n.custody_),
      internet_gateway_(n.internet_gateway_), remote_eid_(n.remote_eid_)
{
}

void
RIBNode::dump(oasys::StringBuffer* buf)
{
    buf->appendf("\tSID %d\n",sid_);
    ProphetNode::dump(buf);
}

void
ProphetNode::dump(oasys::StringBuffer* buf)
{
    buf->appendf("\tEndpointID %s\n"
                 "\tP Value %0.2f\n"
                 "\tRelay %s\n"
                 "\tCustody %s\n"
                 "\tInternet GW %s\n",
                 remote_eid_.c_str(),
                 p_value_,
                 relay_ ? "true" : "false",
                 custody_ ? "true" : "false",
                 internet_gateway_ ? "true" : "false");
}

void
BundleOffer::dump(oasys::StringBuffer* buf)
{
    buf->appendf("\t%s\n"
                 "\tCreation TS %d\n"
                 "\tSID %d\n"
                 "\tCustody %s\n"
                 "\tAccept %s\n"
                 "\tAck %s\n",
                 type_to_str(type_),
                 cts_, sid_,
                 custody_ ? "true" : "false",
                 accept_ ? "true" : "false",
                 ack_ ? "true" : "false");
}

bool
ProphetNode::route_to_me(const EndpointID& eid) const
{
    return Prophet::eid_to_route(remote_eid_).match(eid);
}

/**
 * Equation 1, Section 2.1.1
 * P_(A,B) = P_(A,B)_old + ( 1 - P_(A,B)_old ) * P_encounter
 */
void
ProphetNode::update_pvalue()
{
    // new p_value is P_(A,B), previous p_value_ is P_(A,B)_old
    // params_->encounter_ is P_encounter
    // A is the local node
    // B is the node just encountered, represented by this ProphetNode instance
    ASSERT(p_value_ >= 0.0 && p_value_ <= 1.0);
    double prev = p_value_;
    (void)prev;
    p_value_ = p_value_ + (1.0 - p_value_) * params_->encounter_;
    log_debug("update_pvalue: before %.2f after %.2f",prev,p_value_);
}

/**
 * Equation 3, Section 2.1.1
 * P_(A,C) = P_(A,C)_old + ( 1 - P_(A,C)_old ) * P_(A,B) * P_(B,C) * beta
 */
void
ProphetNode::update_transitive(double ab, double bc)
{
    // new p_value_ is P_(A,C), previous p_value is P_(A,C)_old
    // params_->beta_ is beta
    // A is the local node
    // B is the node just encountered (originator of RIB)
    // C is the node represented by this ProphetNode instance
    ASSERT(p_value_ >= 0.0 && p_value_ <= 1.0);
    ASSERT(ab >= 0.0 && ab <= 1.0);
    ASSERT(bc >= 0.0 && bc <= 1.0);
    double prev = p_value_;
    (void)prev;
    p_value_ = p_value_ + (1.0 - p_value_) * ab * bc * params_->beta_;
    log_debug("update_transitive: ab %.2f bc %.2f before %.2f after %.2f",
              ab,bc,prev,p_value_);
}

/**
 * Equation 2, Section 2.1.1
 * P_(A,B) = P_(A,B)_old * gamma^K
 */
void
ProphetNode::update_age()
{
    // new p_value_ is P_(A,B), previous p_value is P_(A,B)_old
    // params_->gamma_ is gamma
    // timeunits is k
    ASSERT(p_value_ >= 0.0 && p_value_ <= 1.0);
    double gfactor = params_->gamma_;

    oasys::Time now;
    now.get_time();
    u_int timeunits = time_to_units(now - age_);
    age_.get_time();
    
    if ( timeunits == 0 )
        gfactor = 1;
    else if ( timeunits > 1 )
        gfactor = pow( params_->gamma_, timeunits );
    double prev = p_value_;
    (void)prev;
    p_value_ *= gfactor;
    log_debug("update_age: timeunits %u before %.2f after %.2f",
              timeunits,prev,p_value_);
}

u_int
ProphetNode::time_to_units(oasys::Time diff)
{
    // params_->kappa_ is in units of milliseconds per timeunit
    u_int retval = (u_int) diff.sec_ * 1000 / params_->kappa_;
    retval += (u_int) diff.usec_ / (1000 * params_->kappa_);
    log_debug("time_to_units: diff sec %u usec %u timeunits %u",
              diff.sec_,diff.usec_,retval);
    return retval;
}

ProphetAck::ProphetAck()
    : dest_id_(EndpointID::NULL_EID()), cts_(0)
{
}

ProphetAck::ProphetAck(const EndpointID& eid,
                       u_int32_t cts,
                       u_int32_t ets)
    : dest_id_(eid), cts_(cts), ets_(ets)
{
}

ProphetAck::ProphetAck(const ProphetAck& p)
    : dest_id_(p.dest_id_), cts_(p.cts_)
{
    ASSERT(!dest_id_.equals(EndpointID::NULL_EID()));
}

bool
ProphetAck::operator<(const ProphetAck& p) const
{
    if(dest_id_.equals(p.dest_id_))
        return (cts_ < p.cts_);
    return (dest_id_.str() < p.dest_id_.str());
}

ProphetAck&
ProphetAck::operator=(const ProphetAck& p) 
{
    dest_id_ = p.dest_id_;
    cts_ = p.cts_;
    return *this;
}

} // namespace dtn

