/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2012 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: Nicola Baldo <nbaldo@cttc.es>
 */


#include "radio-environment-map-helper.h"

#include <ns3/abort.h>
#include <ns3/log.h>
#include <ns3/double.h>
#include <ns3/integer.h>
#include <ns3/uinteger.h>
#include <ns3/string.h>
#include <ns3/boolean.h>
#include <ns3/spectrum-channel.h>
#include <ns3/config.h>
#include <ns3/rem-spectrum-phy.h>
#include <ns3/mobility-building-info.h>
#include <ns3/constant-position-mobility-model.h>
#include <ns3/simulator.h>
#include <ns3/node.h>
#include <ns3/buildings-helper.h>
#include <ns3/lte-spectrum-value-helper.h>

#include <fstream>
#include <limits>

namespace ns3 {

NS_LOG_COMPONENT_DEFINE ("RadioEnvironmentMapHelper");

NS_OBJECT_ENSURE_REGISTERED (RadioEnvironmentMapHelper);

RadioEnvironmentMapHelper::RadioEnvironmentMapHelper ()
{
}


RadioEnvironmentMapHelper::~RadioEnvironmentMapHelper ()
{
}



void
RadioEnvironmentMapHelper::DoDispose ()
{
  NS_LOG_FUNCTION (this);
}

TypeId
RadioEnvironmentMapHelper::GetTypeId (void)
{
  NS_LOG_FUNCTION ("RadioEnvironmentMapHelper::GetTypeId");
  static TypeId tid = TypeId ("ns3::RadioEnvironmentMapHelper")
    .SetParent<Object> ()
    .SetGroupName("Lte")
    .AddConstructor<RadioEnvironmentMapHelper> ()
    .AddAttribute ("ChannelPath", "The path to the channel for which the Radio Environment Map is to be generated",
                   StringValue ("/ChannelList/0"),
                   MakeStringAccessor (&RadioEnvironmentMapHelper::m_channelPath),
                   MakeStringChecker ())
    .AddAttribute ("OutputFile", "the filename to which the Radio Environment Map is saved",
                   StringValue ("rem.out"),
                   MakeStringAccessor (&RadioEnvironmentMapHelper::m_outputFile),
                   MakeStringChecker ())
    .AddAttribute ("XMin", "The min x coordinate of the map.",
                   DoubleValue (0.0),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_xMin),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("YMin", "The min y coordinate of the map.",
                   DoubleValue (0.0),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_yMin),
                   MakeDoubleChecker<double> ())
   .AddAttribute ("XMax", "The max x coordinate of the map.",
                   DoubleValue (1.0),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_xMax),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("YMax", "The max y coordinate of the map.",
                   DoubleValue (1.0),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_yMax),
                   MakeDoubleChecker<double> ())
   .AddAttribute ("XRes", "The resolution (number of points) of the map along the x axis.",
                   UintegerValue (100),
                   MakeUintegerAccessor (&RadioEnvironmentMapHelper::m_xRes),
                  MakeUintegerChecker<uint32_t> (2,std::numeric_limits<uint16_t>::max ()))
    .AddAttribute ("YRes", "The resolution (number of points) of the map along the y axis.",
                   UintegerValue (100),
                   MakeUintegerAccessor (&RadioEnvironmentMapHelper::m_yRes),
                   MakeUintegerChecker<uint16_t> (2,std::numeric_limits<uint16_t>::max ()))
    .AddAttribute ("Z", "The value of the z coordinate for which the map is to be generated",
		   DoubleValue (0.0),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_z),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("StopWhenDone", "If true, Simulator::Stop () will be called as soon as the REM has been generated",
		   BooleanValue (true),
                   MakeBooleanAccessor (&RadioEnvironmentMapHelper::m_stopWhenDone),
                   MakeBooleanChecker ())
    .AddAttribute ("NoisePower",
                   "the power of the measuring instrument noise, in Watts. Default to a kT of -174 dBm with a noise figure of 9 dB and a bandwidth of 25 LTE Resource Blocks",
                   DoubleValue (1.4230e-13),
                   MakeDoubleAccessor (&RadioEnvironmentMapHelper::m_noisePower),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("MaxPointsPerIteration", "Maximum number of REM points to be calculated per iteration. Every point consumes approximately 5KB of memory.",
                   UintegerValue (20000),
                   MakeUintegerAccessor (&RadioEnvironmentMapHelper::m_maxPointsPerIteration),
                   MakeUintegerChecker<uint32_t> (1,std::numeric_limits<uint32_t>::max ()))
    .AddAttribute ("Earfcn",
                   "E-UTRA Absolute Radio Frequency Channel Number (EARFCN) "
                   "as per 3GPP 36.101 Section 5.7.3. ",
                   UintegerValue (100),
                   MakeUintegerAccessor (&RadioEnvironmentMapHelper::m_earfcn),
                   MakeUintegerChecker<uint16_t> ())
    .AddAttribute ("Bandwidth",
                   "Transmission Bandwidth Configuration (in number of RBs) over which the SINR will be calculated",
                   UintegerValue (25),
                   MakeUintegerAccessor (&RadioEnvironmentMapHelper::SetBandwidth, 
                                         &RadioEnvironmentMapHelper::GetBandwidth),
                   MakeUintegerChecker<uint16_t> ())
    .AddAttribute ("UseDataChannel",
                   "If true, REM will be generated for PDSCH and for PDCCH otherwise ",
                   BooleanValue (false),
                   MakeBooleanAccessor (&RadioEnvironmentMapHelper::m_useDataChannel),
                   MakeBooleanChecker ())
    .AddAttribute ("RbId",
                   "Resource block Id, for which REM will be generated,"
                   "default value is -1, what means REM will be averaged from all RBs",
                   IntegerValue (-1),
                   MakeIntegerAccessor (&RadioEnvironmentMapHelper::m_rbId),
                   MakeIntegerChecker<int32_t> ())
  ;
  return tid;
}


uint8_t 
RadioEnvironmentMapHelper::GetBandwidth () const
{
  return m_bandwidth;
}

void 
RadioEnvironmentMapHelper::SetBandwidth (uint8_t bw)
{
  switch (bw)
    { 
    case 6:
    case 15:
    case 25:
    case 50:
    case 75:
    case 100:
      m_bandwidth = bw;
      break;

    default:
      NS_FATAL_ERROR ("invalid bandwidth value " << (uint16_t) bw);
      break;
    }
}



void 
RadioEnvironmentMapHelper::Install ()
{
  NS_LOG_FUNCTION (this);
  if (!m_rem.empty ())
    {
      NS_FATAL_ERROR ("only one REM supported per instance of RadioEnvironmentMapHelper");
    }
  Config::MatchContainer match = Config::LookupMatches (m_channelPath);
  if (match.GetN () != 1)
    {
      NS_FATAL_ERROR ("Lookup " << m_channelPath << " should have exactly one match");
    }
  m_channel = match.Get (0)->GetObject<SpectrumChannel> ();
  NS_ABORT_MSG_IF (m_channel == 0, "object at " << m_channelPath << "is not of type SpectrumChannel");

  m_outFile.open (m_outputFile.c_str ());
  if (!m_outFile.is_open ())
    {
      NS_FATAL_ERROR ("Can't open file " << (m_outputFile));
      return;
    }
  
  double startDelay = 0.0026;

  if (m_useDataChannel)
    {
      //need time to start transmission of data channel
      startDelay = 0.5001;
    }

  Simulator::Schedule (Seconds (startDelay),
                       &RadioEnvironmentMapHelper::DelayedInstall,
                       this);
}


void 
RadioEnvironmentMapHelper::DelayedInstall ()
{
  NS_LOG_FUNCTION (this);
  m_xStep = (m_xMax - m_xMin)/(m_xRes-1);
  m_yStep = (m_yMax - m_yMin)/(m_yRes-1);
  
  if ((double)m_xRes * (double) m_yRes < (double) m_maxPointsPerIteration)
    {
      m_maxPointsPerIteration = m_xRes * m_yRes;
    }
  
  for (uint32_t i = 0; i < m_maxPointsPerIteration; ++i)
    {
      RemPoint p;
      p.phy = CreateObject<RemSpectrumPhy> ();
      p.bmm = CreateObject<ConstantPositionMobilityModel> ();
      Ptr<MobilityBuildingInfo> buildingInfo = CreateObject<MobilityBuildingInfo> ();
      p.bmm->AggregateObject (buildingInfo); // operation usually done by BuildingsHelper::Install
      p.phy->SetRxSpectrumModel (LteSpectrumValueHelper::GetSpectrumModel (m_earfcn, m_bandwidth));
      p.phy->SetMobility (p.bmm);
      p.phy->SetUseDataChannel (m_useDataChannel);
      p.phy->SetRbId (m_rbId);
      m_channel->AddRx (p.phy);
      m_rem.push_back (p);
    }

  double remIterationStartTime = 0.0001;
  double xMinNext = m_xMin;
  double yMinNext = m_yMin;
  uint32_t numPointsCurrentIteration = 0;
  bool justScheduled = false;
  for (double x = m_xMin; x < m_xMax + 0.5*m_xStep; x += m_xStep)
    {
      for (double y = m_yMin; y < m_yMax + 0.5*m_yStep ; y += m_yStep)
        {
          if (justScheduled)
            {
              xMinNext = x;
              yMinNext = y;
              justScheduled = false;
            }
          
          ++numPointsCurrentIteration;
          if ((numPointsCurrentIteration == m_maxPointsPerIteration)
              || ((x > m_xMax - 0.5*m_xStep) && (y > m_yMax - 0.5*m_yStep)) )
            {
              Simulator::Schedule (Seconds (remIterationStartTime), 
                                   &RadioEnvironmentMapHelper::RunOneIteration,
                                   this, xMinNext, x, yMinNext, y);
              remIterationStartTime += 0.001;
              justScheduled = true;
              numPointsCurrentIteration = 0;
            }
        }      
    }

  Simulator::Schedule (Seconds (remIterationStartTime), 
                       &RadioEnvironmentMapHelper::Finalize,
                       this);
}

  
void 
RadioEnvironmentMapHelper::RunOneIteration (double xMin, double xMax, double yMin, double yMax)
{
  NS_LOG_FUNCTION (this << xMin << xMax << yMin << yMax);
  std::list<RemPoint>::iterator remIt = m_rem.begin ();
  double x;
  double y;
  for (x = xMin; x < xMax + 0.5*m_xStep; x += m_xStep)
    {
      for (y = (x == xMin) ? yMin : m_yMin;
           y < ((x == xMax) ? yMax : m_yMax) + 0.5*m_yStep;
           y += m_yStep)
        {
          NS_ASSERT (remIt != m_rem.end ());
          remIt->bmm->SetPosition (Vector (x, y, m_z));
          BuildingsHelper::MakeConsistent (remIt->bmm);
          ++remIt;
        }      
    }

  if (remIt != m_rem.end ())
    {
      NS_ASSERT ((x > m_xMax - 0.5*m_xStep) && (y > m_yMax - 0.5*m_yStep));
      NS_LOG_LOGIC ("deactivating RemSpectrumPhys that are unneeded in the last iteration");
      while (remIt != m_rem.end ())
        {
          remIt->phy->Deactivate ();
          ++remIt;
        }
    }

  Simulator::Schedule (Seconds (0.0005), &RadioEnvironmentMapHelper::PrintAndReset, this);  
}

void 
RadioEnvironmentMapHelper::PrintAndReset ()
{
  NS_LOG_FUNCTION (this);
  
  for (std::list<RemPoint>::iterator it = m_rem.begin ();
       it != m_rem.end ();
       ++it)
    {
      if (!(it->phy->IsActive ()))
        {
          // should occur only upon last iteration when some RemPoint
          // at the end of the list can be unused
          break;
        }
      Vector pos = it->bmm->GetPosition ();
      NS_LOG_LOGIC ("output: " << pos.x << "\t" 
                    << pos.y << "\t" 
                    << pos.z << "\t" 
                    << it->phy->GetSinr (m_noisePower));
      m_outFile << pos.x << "\t" 
                << pos.y << "\t" 
                << pos.z << "\t" 
                << it->phy->GetSinr (m_noisePower)
                << std::endl;
      it->phy->Reset ();
    }
}

void 
RadioEnvironmentMapHelper::Finalize ()
{
  NS_LOG_FUNCTION (this);
  m_outFile.close ();
  if (m_stopWhenDone)
    {
      Simulator::Stop ();
    }
}


} // namespace ns3
