
/*
 * Copyright 2017-present Open Networking Foundation

 * 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.
 */


'use strict';

angular.module('xos.mcordTopology', [
  'ngResource',
  'ngCookies',
  'ui.router',
  'xos.helpers'
])
.config(($stateProvider) => {
  $stateProvider
  .state('topology', {
    url: '/',
    template: '<m-cord-topology></m-cord-topology>'
  });
})
.config(function($httpProvider){
  $httpProvider.interceptors.push('NoHyperlinks');
})
.factory('_', $window => $window._)
.service('Traffic', function($http, $q){
  this.get = () => {
    var deferred = $q.defer();
    $http.get('videoLocal.txt')
    .then(res => {
      deferred.resolve(res.data);
    })
    .catch(e => {
      console.log(e);
      deferred.resolve(Math.random() * 10)
    });
    return deferred.promise;
  }
})
.directive('mCordTopology', function(){
  return {
    restrict: 'E',
    scope: {},
    bindToController: true,
    controllerAs: 'vm',
    template: '',
    controller: function($element, $interval, $rootScope, _, $http, TopologyElements, NodeDrawer, Traffic){

      const el = $element[0];

      let nodes = [];
      let links = [];
      let traffic = 0;
      let linkWidth = 1;
      let trafficCorrection = 5;

      const filterBBU = (instances) => {
        return _.filter(instances, i => i.name.indexOf("bbu") >= 0);
      };

      const filterOthers = (instances) => {
        return TopologyElements.fakedInstance;
      };

      // retrieving instances list
      const getData = () => {

        d3.select('svg')
          .style('width', `${el.clientWidth}px`)
          .style('height', `${el.clientHeight}px`);

        nodes = TopologyElements.nodes;
        links = TopologyElements.links;

        Traffic.get()
        .then((newTraffic) => {

          // calculating link size
          // it should change between 1 and 10
          if(!traffic){
            linkWidth = 2;
          }
          else if(newTraffic === traffic){
            linkWidth = linkWidth;
          }
          else{
            let delta = newTraffic - traffic;

            if(delta > 0){
              linkWidth = linkWidth + (delta / trafficCorrection);
            }
            else{
              linkWidth = linkWidth - ((delta * -1) / trafficCorrection);
            }

          }

          if(linkWidth < 0.2){
            linkWidth = 0.2
          };

          traffic = newTraffic;

          return $http.get('/api/core/xos/instances');
          // return XosApi.Instance_List_GET()
        })
        .then((instances) => {

          addBbuNodes(filterBBU(instances.data));
          addOtherNodes(filterOthers(instances.data));

          draw(svg, nodes, links);
        })
        .catch((e) => {
          throw new Error(e);
        });
      };

      const force = d3.layout.force();

      // create svg elements
      const svg = d3.select(el)
        .append('svg')
        .style('width', `${el.clientWidth}px`)
        .style('height', `${el.clientHeight}px`);

      const linkContainer = svg.append('g')
        .attr({
          class: 'link-container'
        });

      const nodeContainer = svg.append('g')
        .attr({
          class: 'node-container'
        });

      // replace human readable ids with d3 ids
      // NOTE now ids are not manatined on update...
      const buildLinks = (links, nodes) => {
        return links.map((l) => {

          console.log(_.find);
          let source = _.findIndex(nodes, {id: l.source});
          let target = _.findIndex(nodes, {id: l.target});
          // console.log(`link-${source}-${target}`, source, target);
          return {
            source: source,
            target: target,
            value: 1,
            id: `link-${source}-${target}`,
            type: l.source.indexOf('fabric') >= 0 ? 'big':'small'
          };

        });
      };

      // find fabric nodes and center horizontally
      const positionFabricNodes = (nodes) => {
        return _.map(nodes, n => {
          if(n.type !== 'fabric'){
            return n;
          }

          n.x = n.x * hStep;
          n.y = n.y * vStep;

          return n;
        });
      };

      const addBbuNodes = (instances) => {

        // calculate bbu hStep
        let bbuHStep = ((el.clientWidth / 2) / (instances.length + 1));

        // create nodes
        let bbuNodes = instances.map((n, i) => {
          return {
            type: 'bbu',
            name: n.name,
            id: `bbu-${n.id}`,
            fixed: true,
            y: vStep * 3,
            x: bbuHStep * (i + 1)
          };
        });

        // create links
        let bbuLinks = bbuNodes.map(n => {
          return {
            source: n.id,
            target: 'fabric4'
          };
        });

        // fake RRU nodes and links
        instances.forEach((n, i) => {
          bbuNodes.push({
            type: 'rru',
            name: 'rru',
            id: `rru-${n.id}`,
            fixed: true,
            y: vStep * 4,
            x: bbuHStep * (i + 1)
          });

          bbuLinks.push({
            source: `rru-${n.id}`,
            target: `bbu-${n.id}`
          });
        })

        nodes = nodes.concat(bbuNodes);


        links = links.concat(bbuLinks);
      };

      // add MME, PGW, SGW nodes
      const addOtherNodes = (instances) => {
        let hStep = ((el.clientWidth / 2) / (instances.length + 1));

        // create nodes
        let otherNodes = instances.map((n, i) => {
          return {
            type: n.name.substring(0, 3),
            name: n.name,
            id: `${n.name.substring(0, 3)}-${n.id}`,
            fixed: true,
            y: vStep * 3,
            x: (el.clientWidth / 2) + (hStep * (i + 1))
          };
        });

        // create links
        let otherLinks = otherNodes.map(n => {
          return {
            source: n.id,
            target: 'fabric4'
          };
        });


        nodes = nodes.concat(otherNodes);
        links = links.concat(otherLinks);
      }

      let hStep, vStep;

      hStep = el.clientWidth / 3;
      vStep = el.clientHeight / 5;

      const draw = (svg, nodes, links) => {

        hStep = el.clientWidth / 3;
        vStep = el.clientHeight / 5;

        links = buildLinks(links, nodes);

        nodes = positionFabricNodes(nodes);

        console.log(nodes);
        // start force layout
        force
          .nodes(nodes)
          .links(links)
          .size([el.clientWidth, el.clientHeight])
          .charge(-20)
          .chargeDistance(200)
          .linkDistance(80)
          .linkStrength(0.1)
          .start();


        const linkContainer = d3.select('.link-container');
        const nodeContainer = d3.select('.node-container');

        NodeDrawer.drawFabricBox(nodeContainer, hStep, vStep);

        // draw links
        var link = linkContainer.selectAll('.link')
          .data(links, d => d.id);
        
        link.enter().append('line')
          .attr({
            class: d => `link ${d.type}`,
            'stroke-width': linkWidth,
            id: d => d.id,
            opacity: 0
          })
          .transition()
          .duration(1000)
          .attr({
            opacity: 1
          });

        link
          .transition()
          .duration(1000)
          .attr({
            'stroke-width': linkWidth,
            opacity: 1
          });

        link.exit()
        .remove();

        //draw nodes
        var node = nodeContainer.selectAll('.node')
          .data(nodes, d => {
            return d.id
          });
        
        // append a group for any new node
        var enter = node.enter()
          .append('g', d => d.interfaceCfgIdentifier)
          .attr({
            class: d => `${d.type} node`,
            transform: d => `translate(${d.x}, ${d.y})`
          });

        // draw nodes
        NodeDrawer.drawBbus(enter.filter('.bbu'))
        NodeDrawer.drawRrus(enter.filter('.rru'))
        NodeDrawer.drawFabric(enter.filter('.fabric'))
        NodeDrawer.drawOthers(enter.filter(d => {
          console.log(d.type);
          return (
            d.type  === 'MME' ||
            d.type === 'SGW' ||
            d.type === 'PGW' ||
            d.type === 'Vid'
          )
        }));

        // remove nodes
        var exit = node.exit();

        NodeDrawer.removeElements(exit);

        force.on('tick', function() {
          link
            .attr('x1', d => d.source.x )
            .attr('y1', d => d.source.y )
            .attr('x2', d => d.target.x )
            .attr('y2', d => d.target.y );

          node.attr('transform', (d) => `translate(${d.x},${d.y})`);
        });
      };
      
      // $interval(() => {
      //   getData();
      // }, 3000);
      getData();

      
    }
  };
});
