/*-*-	Mode:C++; c-basic-offset:8; tab-width:8; indent-tabs-mode:t -*- */

/* Modified and extended by Pablo Martin and Paula Ballester,
 * Strathclyde University, Glasgow.
 * June, 2003.
*/

/* Copyright (c) 2003 Strathclyde University of Glasgow, Scotland.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code and binary code must contain
 * the above copyright notice, this list of conditions and the following
 * disclaimer.
 *
 * 2. All advertising materials mentioning features or use of this software
 * must display the following acknowledgement:
 * This product includes software developed at Strathclyde University of
 * Glasgow, Scotland.
 *
 * 3. The name of the University may not be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 * STRATHCLYDE UNIVERSITY OF GLASGOW, MAKES NO REPRESENTATIONS
 * CONCERNING EITHER THE MERCHANTABILITY OF THIS SOFTWARE OR THE
 * SUITABILITY OF THIS SOFTWARE FOR ANY PARTICULAR PURPOSE.  The software
 * is provided "as is" without express or implied warranty of any kind.
 *
 *
 * Copyright (c) 1997 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the Computer Systems
 *	Engineering Group at Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Header: /nfs/jade/vint/CVSROOT/ns-2/common/mobilenode.cc,v 1.29 2002/05/06 17:10:40 difa Exp $
 *
 * Code in this file will be changed in the near future. From now on it
 * should be treated as for backward compatibility only, although it is in
 * active use by many other code in ns. - Aug 29, 2000
 */

/*
 * CMU-Monarch project's Mobility extensions ported by Padma Haldar,
 * 11/98.
 */


#include <math.h>
#include <stdlib.h>

#include "connector.h"
#include "delay.h"
#include "packet.h"
#include "random.h"
#include "trace.h"
#include "address.h"

#include "arp.h"
#include "topography.h"
#include "ll.h"
#include "mac.h"
#include "propagation.h"
#include "mobilenode.h"
#include "phy.h"
#include "wired-phy.h"
#include "god.h"

// XXX Must supply the first parameter in the macro otherwise msvc
// is unhappy.
static LIST_HEAD(_dummy_MobileNodeList, MobileNode) nodehead = { 0 };

static class MobileNodeClass : public TclClass {
public:
        MobileNodeClass() : TclClass("Node/MobileNode") {}
        TclObject* create(int, const char*const*) {
                return (new MobileNode);
        }
} class_mobilenode;

/*
 *  PositionHandler()
 *
 *  Updates the position of a mobile node every N seconds, where N is
 *  based upon the speed of the mobile and the resolution of the topography.
 *
 */
void
PositionHandler::handle(Event*)
{
	Scheduler& s = Scheduler::instance();

#if 0
	fprintf(stderr, "*** POSITION HANDLER for node %d (time: %f) ***\n",
		node->address(), s.clock());
#endif
	/*
	 * Update current location
	 */

	node->update_position();

	/*
	 * Choose a new random speed and direction
	 */
#ifdef DEBUG
        fprintf(stderr, "%d - %s: calling random_destination()\n",
                node->address_, __PRETTY_FUNCTION__);
#endif
	node->random_destination();

	s.schedule(&node->pos_handle_, &node->pos_intr_,
		   node->position_update_interval_);
}

/* ======================================================================
   Mobile Node
   ====================================================================== */

MobileNode::MobileNode(void) :
	pos_handle_(this)
{
	X_ = Y_ = Z_ = speed_ = 0.0;
	dX_ = dY_ = dZ_ = 0.0;
	destX_ = destY_ = 0.0;

	random_motion_ = 0;
	base_stn_ = -1;
	T_ = 0;

	ll_ = 0;
	log_target_ = 0;
	next_ = 0;
	radius_ = 0;

	position_update_interval_ = POSITION_UPDATE_INTERVAL;
	position_update_time_ = 0.0;


	LIST_INSERT_HEAD(&nodehead, this, link_);	// node list
	LIST_INIT(&ifhead_);				// interface list
	bind("X_", &X_);
	bind("Y_", &Y_);
	bind("Z_", &Z_);
	bind("speed_", &speed_);
}

int
MobileNode::command(int argc, const char*const* argv)
{
	Tcl& tcl = Tcl::instance();
	if(argc == 2) {
		if(strcmp(argv[1], "ON") == 0) {
			ll_->switch_on();
			return TCL_OK;
		}
		if(strcmp(argv[1], "start") == 0) {
		        start();
			return TCL_OK;
		} else if(strcmp(argv[1], "log-movement") == 0) {
#ifdef DEBUG
                        fprintf(stderr,
                                "%d - %s: calling update_position()\n",
                                address_, __PRETTY_FUNCTION__);
#endif

			  update_position();
		        log_movement();
			return TCL_OK;
		} else if(strcmp(argv[1], "log-energy") == 0) {
			log_energy(1);
			return TCL_OK;
		} else if(strcmp(argv[1], "powersaving") == 0) {
			energy_model()->powersavingflag() = 1;
			energy_model()->start_powersaving();
			return TCL_OK;
		} else if(strcmp(argv[1], "adaptivefidelity") == 0) {
			energy_model()->adaptivefidelity() = 1;
			energy_model()->powersavingflag() = 1;
			energy_model()->start_powersaving();
			return TCL_OK;
		} else if (strcmp(argv[1], "energy") == 0) {
			Tcl& tcl = Tcl::instance();
			tcl.resultf("%f", energy_model()->energy());
			return TCL_OK;
		} else if (strcmp(argv[1], "adjustenergy") == 0) {
			// assume every 10 sec schedule and 1.15 W
			// idle energy consumption. needs to be
			// parameterized.
			idle_energy_patch(10, 1.15);
			energy_model()->total_sndtime() = 0;
			energy_model()->total_rcvtime() = 0;
			energy_model()->total_sleeptime() = 0;
			return TCL_OK;
		} else if (strcmp(argv[1], "on") == 0) {
			energy_model()->node_on() = true;
			tcl.evalf("%s set netif_(0)", name_);
			char *str = tcl.result();
			tcl.evalf("%s NodeOn", str);
			God::instance()->ComputeRoute();
			return TCL_OK;
		} else if (strcmp(argv[1], "off") == 0) {
			energy_model()->node_on() = false;
			tcl.evalf("%s set netif_(0)", name_);
			char *str = tcl.result();
			tcl.evalf("%s NodeOff", str);
			tcl.evalf("%s set ragent_", name_);
			str = tcl.result();
			tcl.evalf("%s reset-state", str);
			God::instance()->ComputeRoute();
		     	return TCL_OK;
		} else if (strcmp(argv[1], "shutdown") == 0) {
			// set node state
			//Phy *p;
			energy_model()->node_on() = false;

			//p = ifhead().lh_first;
			//if (p) ((WirelessPhy *)p)->node_off();
			return TCL_OK;
		} else if (strcmp(argv[1], "startup") == 0) {
			energy_model()->node_on() = true;
			return TCL_OK;
		}

	} else if(argc == 3) {
		if(strcmp(argv[1], "addif") == 0) {
			WiredPhy* phyp = (WiredPhy*)TclObject::lookup(argv[2]);
			if(phyp == 0)
				return TCL_ERROR;
			phyp->insertnode(&ifhead_);
			phyp->setnode(this);
			return TCL_OK;
		} else if (strcmp(argv[1], "ll") == 0) {
			ll_ = (LL*)TclObject::lookup(argv[2]);
			return TCL_OK;
		} else if (strcmp(argv[1], "setsleeptime") == 0) {
			energy_model()->afe()->set_sleeptime(atof(argv[2]));
			energy_model()->afe()->set_sleepseed(atof(argv[2]));
			return TCL_OK;
		} else if (strcmp(argv[1], "setenergy") == 0) {
			energy_model()->setenergy(atof(argv[2]));
			return TCL_OK;
		} else if (strcmp(argv[1], "settalive") == 0) {
			energy_model()->max_inroute_time() = atof(argv[2]);
			return TCL_OK;
		} else if (strcmp(argv[1], "maxttl") == 0) {
			energy_model()->maxttl() = atoi(argv[2]);
			return TCL_OK;
		} else if(strcmp(argv[1], "radius") == 0) {
                        radius_ = strtod(argv[2],NULL);
                        return TCL_OK;
                } else if(strcmp(argv[1], "random-motion") == 0) {
			random_motion_ = atoi(argv[2]);
			return TCL_OK;
		} else if(strcmp(argv[1], "addif") == 0) {
			WirelessPhy *n = (WirelessPhy*)
				TclObject::lookup(argv[2]);
			if(n == 0)
				return TCL_ERROR;
			n->insertnode(&ifhead_);
			n->setnode(this);
			return TCL_OK;
		} else if(strcmp(argv[1], "topography") == 0) {
			T_ = (Topography*) TclObject::lookup(argv[2]);
			if (T_ == 0)
				return TCL_ERROR;
			return TCL_OK;
		} else if(strcmp(argv[1], "log-target") == 0) {
			log_target_ = (Trace*) TclObject::lookup(argv[2]);
			if (log_target_ == 0)
				return TCL_ERROR;
			return TCL_OK;
		} else if (strcmp(argv[1],"base-station") == 0) {
			base_stn_ = atoi(argv[2]);
			if(base_stn_ == -1)
				return TCL_ERROR;
			return TCL_OK;
		}
	} else if (argc == 4) {
		if (strcmp(argv[1], "idleenergy") == 0) {
			idle_energy_patch(atof(argv[2]),atof(argv[3]));
			return TCL_OK;
		}
	} else if (argc == 5) {
		if (strcmp(argv[1], "setdest") == 0) {
			/* <mobilenode> setdest <X> <Y> <speed> */
#ifdef DEBUG
			fprintf(stderr, "%d - %s: calling set_destination()\n",
				address_, __FUNCTION__);
#endif

			if (set_destination(atof(argv[2]), atof(argv[3]),
					    atof(argv[4])) < 0)
				return TCL_ERROR;
			return TCL_OK;
		}
		// P.M. & P.B. modifications
		if (strcmp(argv[3], "rate_") == 0) {
			tcl.evalf("%s nodetype", name_);
			char* ni = tcl.result();
			if (atoi(ni) != 1)
				return TCL_ERROR;
			ll_->configure_flow(atoi(argv[1]), argv[2], bw_atof(argv[4]));
			return TCL_OK;
		}
	}
	return Node::command(argc, argv);
}


/* ======================================================================
   Other class functions
   ====================================================================== */
void
MobileNode::dump(void)
{
	Phy *n;
	fprintf(stdout, "Index: %d\n", address_);
	fprintf(stdout, "Network Interface List\n");
 	for(n = ifhead_.lh_first; n; n = n->nextnode() )
		n->dump();	
	fprintf(stdout, "--------------------------------------------------\n");
}

/* ======================================================================
   Position Functions
   ====================================================================== */
void 
MobileNode::start()
{
	Scheduler& s = Scheduler::instance();

	if(random_motion_ == 0) {
		log_movement();
		return;
	}

	assert(initialized());

	random_position();
#ifdef DEBUG
        fprintf(stderr, "%d - %s: calling random_destination()\n",
                address_, __PRETTY_FUNCTION__);
#endif
	random_destination();
	s.schedule(&pos_handle_, &pos_intr_, position_update_interval_);
}

void 
MobileNode::log_movement()
{
        if (!log_target_) 
		return;

	Scheduler& s = Scheduler::instance();
	sprintf(log_target_->pt_->buffer(),
		"M %.5f %d (%.2f, %.2f, %.2f), (%.2f, %.2f), %.2f",
		s.clock(), address_, X_, Y_, Z_, destX_, destY_, speed_);
	log_target_->pt_->dump();
}


void
MobileNode::log_energy(int flag)
{
	if (!log_target_) 
		return;
	Scheduler &s = Scheduler::instance();
	if (flag) {
		sprintf(log_target_->pt_->buffer(),"N -t %f -n %d -e %f", s.clock(),
			address_, energy_model_->energy()); 
	} else {
		sprintf(log_target_->pt_->buffer(),"N -t %f -n %d -e 0 ", s.clock(),
			address_); 
	}
	log_target_->pt_->dump();
}

//void
//MobileNode::logrttime(double t)
//{
//	last_rt_time_ = (int)t;
//}

void
MobileNode::bound_position()
{
	double minX;
	double maxX;
	double minY;
	double maxY;
	int recheck = 1;

	assert(T_ != 0);

	minX = T_->lowerX();
	maxX = T_->upperX();
	minY = T_->lowerY();
	maxY = T_->upperY();

	while (recheck) {
		recheck = 0;
		if (X_ < minX) {
			X_ = minX + (minX - X_);
			recheck = 1;
		}
		if (X_ > maxX) {
			X_ = maxX - (X_ - maxX);
			recheck = 1;
		}
		if (Y_ < minY) {
			Y_ = minY + (minY - Y_);
			recheck = 1;
		}
		if (Y_ > maxY) {
			Y_ = maxY- (Y_ - maxY);
			recheck = 1;
		}
		if (recheck) {
			fprintf(stderr, "Adjust position of node %d\n",address_);
			recheck = 0;
		}
	}
}

int
MobileNode::set_destination(double x, double y, double s)
{
	assert(initialized());

	if(x >= T_->upperX() || x <= T_->lowerX())
		return -1;
	if(y >= T_->upperY() || y <= T_->lowerY())
		return -1;

	update_position();	// figure out where we are now

	destX_ = x;
	destY_ = y;
	speed_ = s;

	dX_ = destX_ - X_;
	dY_ = destY_ - Y_;
	dZ_ = 0.0;		// this isn't used, since flying isn't allowed

	if (destX_ != X_ || destY_ != Y_) {
		// normalize dx, dy to unit len
		double len = sqrt( (dX_ * dX_) + (dY_ * dY_) );
		dX_ /= len;
		dY_ /= len;
	}

	position_update_time_ = Scheduler::instance().clock();

#ifdef DEBUG
	fprintf(stderr, "%d - %s: calling log_movement()\n",
		address_, __FUNCTION__);
#endif
	log_movement();

	/* update gridkeeper */
	if (GridKeeper::instance()){
		GridKeeper* gp =  GridKeeper::instance();
		gp-> new_moves(this);
	}

	if (namChan_ != 0) {
		sprintf(nwrk_,
			"n -t %f -s %d -x %f -y %f -u %f -V %f -T %f",
			Scheduler::instance().clock(),
			nodeid_,
			X_, Y_,
			speed_ * dX_, speed_ * dY_,
			((speed_*dX_) != 0) ?
				(destX_-X_)/(speed_*dX_) : speed_*dX_
			);
		namdump();
	}
	return 0;
}

void
MobileNode::update_position()
{
	double now = Scheduler::instance().clock();
	double interval = now - position_update_time_;

	if (interval == 0.0)
		return;
	
	X_ += dX_ * (speed_ * interval);
	Y_ += dY_ * (speed_ * interval);

	if ((dX_ > 0 && X_ > destX_) || (dX_ < 0 && X_ < destX_))
	  X_ = destX_;		// correct overshoot (slow? XXX)
	if ((dY_ > 0 && Y_ > destY_) || (dY_ < 0 && Y_ < destY_))
	  Y_ = destY_;		// correct overshoot (slow? XXX)

	bound_position();

	Z_ = T_->height(X_, Y_);
#if 0
	fprintf(stderr, "Node: %d, X: %6.2f, Y: %6.2f, Z: %6.2f, time: %f\n",
		address_, X_, Y_, Z_, now);
#endif
	position_update_time_ = now;

}


void
MobileNode::random_position()
{
	if (T_ == 0) {
		fprintf(stderr, "No TOPOLOGY assigned\n");
		exit(1);
	}

	X_ = Random::uniform() * T_->upperX();
	Y_ = Random::uniform() * T_->upperY();
	Z_ = T_->height(X_, Y_);

	position_update_time_ = 0.0;
}

void
MobileNode::random_destination()
{
	if (T_ == 0) {
		fprintf(stderr, "No TOPOLOGY assigned\n");
		exit(1);
	}

	random_speed();
#ifdef DEBUG
        fprintf(stderr, "%d - %s: calling set_destination()\n",
                address_, __FUNCTION__);
#endif
	(void) set_destination(Random::uniform() * T_->upperX(),
                               Random::uniform() * T_->upperY(),
                               speed_);
}

void
MobileNode::random_direction()
{
	/* this code isn't used anymore -dam 1/22/98 */
	double len;

	dX_ = (double) Random::random();
	dY_ = (double) Random::random();

	len = sqrt( (dX_ * dX_) + (dY_ * dY_) );

	dX_ /= len;
	dY_ /= len;
	dZ_ = 0.0;				// we're not flying...

	/*
	 * Determine the sign of each component of the
	 * direction vector.
	 */
	if (X_ > (T_->upperX() - 2*T_->resol())) {
		if (dX_ > 0) 
			dX_ = -dX_;
	} else if (X_ < (T_->lowerX() + 2*T_->resol())) {
		if (dX_ < 0) 
			dX_ = -dX_;
	} else if (Random::uniform() <= 0.5) {
		dX_ = -dX_;
	}

	if (Y_ > (T_->upperY() - 2*T_->resol())) {
		if (dY_ > 0) 
			dY_ = -dY_;
	} else if (Y_ < (T_->lowerY() + 2*T_->resol())) {
		if (dY_ < 0) 
			dY_ = -dY_;
	} else if(Random::uniform() <= 0.5) {
		dY_ = -dY_;
	}
#if 0
	fprintf(stderr, "Location: (%f, %f), Direction: (%f, %f)\n",
		X_, Y_, dX_, dY_);
#endif
}

void
MobileNode::random_speed()
{
	speed_ = Random::uniform() * MAX_SPEED;
}

double
MobileNode::distance(MobileNode *m)
{
	update_position();		// update my position
	m->update_position();		// update m's position

        double Xpos = (X_ - m->X_) * (X_ - m->X_);
        double Ypos = (Y_ - m->Y_) * (Y_ - m->Y_);
	double Zpos = (Z_ - m->Z_) * (Z_ - m->Z_);

        return sqrt(Xpos + Ypos + Zpos);
}

double
MobileNode::propdelay(MobileNode *m)
{
	return distance(m) / SPEED_OF_LIGHT;
}

void
MobileNode::idle_energy_patch(float /*total*/, float /*P_idle*/)
{
}

// P.M. & P.B. modifications
void MobileNode::getLoc(double *x, double *y, double *z) {
	update_position();
	*x = X_;
	*y = Y_;
	*z = Z_;
}

// P.M. & P.B. modifications
void MobileNode::getVelo(double *dx, double *dy, double *dz) {
	*dx = dX_ * speed_;
	*dy = dY_ * speed_;
	*dz = 0.0;
}

