/*
 * 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: John Abraham <john.abraham.in@gmail.com>
 */

#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/netanim-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-layout-module.h"
#include "ns3/point-to-point-module.h"

#include <iostream>

using namespace ns3;

AnimationInterface* pAnim = nullptr;

/// RGB structure
struct Rgb
{
    uint8_t r; ///< red
    uint8_t g; ///< green
    uint8_t b; ///< blue
};

Rgb colors[] = {
    {255, 0, 0}, // Red
    {0, 255, 0}, // Blue
    {0, 0, 255}, // Green
};

void
modify()
{
    std::ostringstream oss;
    oss << "Update:" << Simulator::Now().GetSeconds();
    pAnim->UpdateLinkDescription(0, 1, oss.str());
    pAnim->UpdateLinkDescription(0, 2, oss.str());
    pAnim->UpdateLinkDescription(0, 3, oss.str());
    pAnim->UpdateLinkDescription(0, 4, oss.str());
    pAnim->UpdateLinkDescription(0, 5, oss.str());
    pAnim->UpdateLinkDescription(0, 6, oss.str());
    pAnim->UpdateLinkDescription(1, 7, oss.str());
    pAnim->UpdateLinkDescription(1, 8, oss.str());
    pAnim->UpdateLinkDescription(1, 9, oss.str());
    pAnim->UpdateLinkDescription(1, 10, oss.str());
    pAnim->UpdateLinkDescription(1, 11, oss.str());

    // Every update change the node description for node 2
    std::ostringstream node0Oss;
    node0Oss << "-----Node:" << Simulator::Now().GetSeconds();
    pAnim->UpdateNodeDescription(2, node0Oss.str());

    // Every update change the color for node 4
    static uint32_t index = 0;
    index++;
    if (index == 3)
    {
        index = 0;
    }
    Rgb color = colors[index];
    for (uint32_t nodeId = 4; nodeId < 12; ++nodeId)
    {
        pAnim->UpdateNodeColor(nodeId, color.r, color.g, color.b);
    }

    if (Simulator::Now().GetSeconds() < 10)
    { // This is important or the simulation
        // will run endlessly
        Simulator::Schedule(Seconds(1), modify);
    }
}

int
main(int argc, char* argv[])
{
    Config::SetDefault("ns3::OnOffApplication::PacketSize", UintegerValue(512));
    Config::SetDefault("ns3::OnOffApplication::DataRate", StringValue("500kb/s"));

    uint32_t nLeftLeaf = 5;
    uint32_t nRightLeaf = 5;
    uint32_t nLeaf = 0;                            // If non-zero, number of both left and right
    std::string animFile = "dynamic_linknode.xml"; // Name of file for animation output

    CommandLine cmd(__FILE__);
    cmd.AddValue("nLeftLeaf", "Number of left side leaf nodes", nLeftLeaf);
    cmd.AddValue("nRightLeaf", "Number of right side leaf nodes", nRightLeaf);
    cmd.AddValue("nLeaf", "Number of left and right side leaf nodes", nLeaf);
    cmd.AddValue("animFile", "File Name for Animation Output", animFile);

    cmd.Parse(argc, argv);
    if (nLeaf > 0)
    {
        nLeftLeaf = nLeaf;
        nRightLeaf = nLeaf;
    }

    // Create the point-to-point link helpers
    PointToPointHelper pointToPointRouter;
    pointToPointRouter.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
    pointToPointRouter.SetChannelAttribute("Delay", StringValue("1ms"));
    PointToPointHelper pointToPointLeaf;
    pointToPointLeaf.SetDeviceAttribute("DataRate", StringValue("10Mbps"));
    pointToPointLeaf.SetChannelAttribute("Delay", StringValue("1ms"));

    PointToPointDumbbellHelper d(nLeftLeaf,
                                 pointToPointLeaf,
                                 nRightLeaf,
                                 pointToPointLeaf,
                                 pointToPointRouter);

    // Install Stack
    InternetStackHelper stack;
    d.InstallStack(stack);

    // Assign IP Addresses
    d.AssignIpv4Addresses(Ipv4AddressHelper("10.1.1.0", "255.255.255.0"),
                          Ipv4AddressHelper("10.2.1.0", "255.255.255.0"),
                          Ipv4AddressHelper("10.3.1.0", "255.255.255.0"));

    d.BoundingBox(1, 1, 100, 100);
    // Install on/off app on all right side nodes
    OnOffHelper clientHelper("ns3::UdpSocketFactory", Address());
    clientHelper.SetAttribute("OnTime", StringValue("ns3::UniformRandomVariable[Min=0.|Max=1.]"));
    clientHelper.SetAttribute("OffTime", StringValue("ns3::UniformRandomVariable[Min=0.|Max=1.]"));
    ApplicationContainer clientApps;

    for (uint32_t i = 0; i < d.RightCount(); ++i)
    {
        // Create an on/off app sending packets to the same leaf right side
        AddressValue remoteAddress(InetSocketAddress(d.GetLeftIpv4Address(i), 1000));
        clientHelper.SetAttribute("Remote", remoteAddress);
        clientApps.Add(clientHelper.Install(d.GetRight(i)));
    }

    clientApps.Start(Seconds(0.0));
    clientApps.Stop(Seconds(10.0));

    // Set the bounding box for animation

    // Create the animation object and configure for specified output
    pAnim = new AnimationInterface(animFile);
    Simulator::Schedule(Seconds(1), modify);

    // Set up the actual simulation
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    Simulator::Run();
    std::cout << "Animation Trace file created:" << animFile << std::endl;
    Simulator::Destroy();
    delete pAnim;
    return 0;
}
