/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * 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
 *
 * Author: Giuseppe Piro  <g.piro@poliba.it>
 *         Marco Miozzo <mmiozzo@cttc.es>
 */


#include <ns3/trace-fading-loss-model.h>
#include <ns3/mobility-model.h>
#include <ns3/spectrum-value.h>
#include <ns3/log.h>
#include <ns3/string.h>
#include <ns3/double.h>
#include "ns3/uinteger.h"
#include <fstream>
#include <ns3/simulator.h>

NS_LOG_COMPONENT_DEFINE ("TraceFadingLossModel");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (TraceFadingLossModel);
  


TraceFadingLossModel::TraceFadingLossModel ()
{
  NS_LOG_FUNCTION (this);
  SetNext (NULL);
}


TraceFadingLossModel::~TraceFadingLossModel ()
{
  m_fadingTrace.clear ();
  m_windowOffsetsMap.clear ();
  m_startVariableMap.clear ();
}


TypeId
TraceFadingLossModel::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::TraceFadingLossModel")
    .SetParent<SpectrumPropagationLossModel> ()
    .AddConstructor<TraceFadingLossModel> ()
    .AddAttribute ("TraceFilename",
                   "Name of file to load a trace from.",
                   StringValue (""),
                   MakeStringAccessor (&TraceFadingLossModel::SetTraceFileName),
                   MakeStringChecker ())
    .AddAttribute ("TraceLength",
                  "The total length of the fading trace (default value 10 s.)",
                  TimeValue (Seconds (10.0)),
                  MakeTimeAccessor (&TraceFadingLossModel::SetTraceLength),
                  MakeTimeChecker ())
    .AddAttribute ("SamplesNum",
                  "The number of samples the trace is made of (default 10000)",
                   UintegerValue (10000),
                   MakeUintegerAccessor (&TraceFadingLossModel::m_samplesNum),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("WindowSize",
                  "The size of the window for the fading trace (default value 0.5 s.)",
                  TimeValue (Seconds (0.5)),
                  MakeTimeAccessor (&TraceFadingLossModel::m_windowSize),
                  MakeTimeChecker ())
    .AddAttribute ("RbNum",
                    "The number of RB the trace is made of (default 100)",
                    UintegerValue (100),
                   MakeUintegerAccessor (&TraceFadingLossModel::m_rbNum),
                   MakeUintegerChecker<uint8_t> ())
  ;
  return tid;
}

void
TraceFadingLossModel::SetTraceFileName (std::string fileName)
{
  NS_LOG_FUNCTION (this << "Set Fading Trace " << fileName);
  
  m_traceFile = fileName;
}

void 
TraceFadingLossModel::SetTraceLength (Time t)
{
  m_traceLength = t;
}

void 
TraceFadingLossModel::DoInitialize ()
{
  LoadTrace ();
}


void
TraceFadingLossModel::LoadTrace ()
{
  NS_LOG_FUNCTION (this << "Loading Fading Trace " << m_traceFile);
  std::ifstream ifTraceFile;
  ifTraceFile.open (m_traceFile.c_str (), std::ifstream::in);
  m_fadingTrace.clear ();
  if (!ifTraceFile.good ())
    {
      NS_LOG_INFO (this << " File: " << m_traceFile);
      NS_ASSERT_MSG(ifTraceFile.good (), " Fading trace file not found");
    }

//   NS_LOG_INFO (this << " length " << m_traceLength.GetSeconds ());
//   NS_LOG_INFO (this << " RB " << (uint32_t)m_rbNum << " samples " << m_samplesNum);
  for (uint32_t i = 0; i < m_rbNum; i++)
    {
      FadingTraceSample rbTimeFadingTrace;
      for (uint32_t j = 0; j < m_samplesNum; j++)
        {
          double sample;
          ifTraceFile >> sample;
          rbTimeFadingTrace.push_back (sample);
        }
      m_fadingTrace.push_back (rbTimeFadingTrace);
    }
  m_timeGranularity = m_traceLength.GetMilliSeconds () / m_samplesNum;
  m_lastWindowUpdate = Simulator::Now ();
}


Ptr<SpectrumValue>
TraceFadingLossModel::DoCalcRxPowerSpectralDensity (
  Ptr<const SpectrumValue> txPsd,
  Ptr<const MobilityModel> a,
  Ptr<const MobilityModel> b) const
{
  NS_LOG_FUNCTION (this << *txPsd << a << b);
  
  std::map <ChannelRealizationId_t, int >::iterator itOff;
  ChannelRealizationId_t mobilityPair = std::make_pair (a,b);
  itOff = m_windowOffsetsMap.find (mobilityPair);
  if (itOff!=m_windowOffsetsMap.end ())
    {
      if (Simulator::Now ().GetSeconds () >= m_lastWindowUpdate.GetSeconds () + m_windowSize.GetSeconds ())
        {
          // update all the offsets
          NS_LOG_INFO ("Fading Windows Updated");
          std::map <ChannelRealizationId_t, int >::iterator itOff2;
          for (itOff2 = m_windowOffsetsMap.begin (); itOff2 != m_windowOffsetsMap.end (); itOff2++)
            {
              std::map <ChannelRealizationId_t, Ptr<UniformRandomVariable> >::iterator itVar;
              itVar = m_startVariableMap.find ((*itOff2).first);
              (*itOff2).second = (*itVar).second->GetValue ();
            }
          m_lastWindowUpdate = Simulator::Now ();
        }
    }
  else
    {
      NS_LOG_LOGIC (this << "insert new channel realization, m_windowOffsetMap.size () = " << m_windowOffsetsMap.size ());
      Ptr<UniformRandomVariable> startV = CreateObject<UniformRandomVariable> ();
      startV->SetAttribute ("Min", DoubleValue (1.0));
      startV->SetAttribute ("Max", DoubleValue ((m_traceLength.GetSeconds () - m_windowSize.GetSeconds ()) * 1000.0));
      ChannelRealizationId_t mobilityPair = std::make_pair (a,b);
      m_startVariableMap.insert (std::pair<ChannelRealizationId_t,Ptr<UniformRandomVariable> > (mobilityPair, startV));
      m_windowOffsetsMap.insert (std::pair<ChannelRealizationId_t,int> (mobilityPair, startV->GetValue ()));
    }

  
  Ptr<SpectrumValue> rxPsd = Copy<SpectrumValue> (txPsd);
  Values::iterator vit = rxPsd->ValuesBegin ();
  
  //Vector aSpeedVector = a->GetVelocity ();
  //Vector bSpeedVector = b->GetVelocity ();
  
  //double speed = std::sqrt (std::pow (aSpeedVector.x-bSpeedVector.x,2) + std::pow (aSpeedVector.y-bSpeedVector.y,2));

  NS_LOG_LOGIC (this << *rxPsd);
  NS_ASSERT (!m_fadingTrace.empty ());
  int now_ms = static_cast<int> (Simulator::Now ().GetMilliSeconds () * m_timeGranularity);
  int lastUpdate_ms = static_cast<int> (m_lastWindowUpdate.GetMilliSeconds () * m_timeGranularity);
  int index = ((*itOff).second + now_ms - lastUpdate_ms) % m_samplesNum;
  int subChannel = 0;
  while (vit != rxPsd->ValuesEnd ())
    {
      NS_ASSERT (subChannel < 100);
      if (*vit != 0.)
        {
          double fading = m_fadingTrace.at (subChannel).at (index);
          NS_LOG_INFO (this << " FADING now " << now_ms << " offset " << (*itOff).second << " id " << index << " fading " << fading);
          double power = *vit; // in Watt/Hz
          power = 10 * std::log10 (180000 * power); // in dB

          NS_LOG_LOGIC (this << subChannel << *vit  << power << fading);

          *vit = std::pow (10., ((power + fading) / 10)) / 180000; // in Watt

          NS_LOG_LOGIC (this << subChannel << *vit);

        }

      ++vit;
      ++subChannel;

    }

  NS_LOG_LOGIC (this << *rxPsd);
  return rxPsd;
}

int64_t
TraceFadingLossModel::AssignStreams (int64_t stream)
{
  NS_LOG_FUNCTION (this << stream);
  int64_t currentStream = stream;
  std::map <ChannelRealizationId_t, Ptr<UniformRandomVariable> >::iterator itVar;
  itVar = m_startVariableMap.begin ();
  while (itVar!=m_startVariableMap.end ())
    {
      (*itVar).second->SetStream (currentStream);
      currentStream += 1;
    }
  return (currentStream - stream);
}



} // namespace ns3
