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

/* global Hogan */
/* Namespace for core functionality related to Network Topology. */

horizon.flat_network_topology = {
  model: null,
  fa_globe_glyph: '\uf0ac',
  fa_globe_glyph_width: 15,
  svg:'#topology_canvas',
  svg_container:'#flatTopologyCanvasContainer',
  network_tmpl:{
    small:'#topology_template > .network_container_small',
    normal:'#topology_template > .network_container_normal'
  },
  router_tmpl: {
    small:'#topology_template > .router_small',
    normal:'#topology_template > .router_normal'
  },
  instance_tmpl: {
    small:'#topology_template > .instance_small',
    normal:'#topology_template > .instance_normal'
  },
  balloon_tmpl : null,
  balloon_device_tmpl : null,
  balloon_port_tmpl : null,
  network_index: {},
  balloon_id:null,
  reload_duration: 10000,
  draw_mode:'normal',
  network_height : 0,
  previous_message : null,
  element_properties:{
    normal:{
      network_width:270,
      network_min_height:500,
      top_margin:80,
      default_height:50,
      margin:20,
      device_x:98.5,
      device_width:90,
      port_margin:16,
      port_height:6,
      port_width:82,
      port_text_margin:{x:6,y:-4},
      texts_bg_y:32,
      type_y:46,
      balloon_margin:{x:12,y:-12}
    },
    small :{
      network_width:100,
      network_min_height:400,
      top_margin:50,
      default_height:20,
      margin:30,
      device_x:47.5,
      device_width:20,
      port_margin:5,
      port_height:3,
      port_width:32.5,
      port_text_margin:{x:0,y:0},
      texts_bg_y:0,
      type_y:0,
      balloon_margin:{x:12,y:-30}
    },
    cidr_margin:5,
    device_name_max_size:9,
    device_name_suffix:'..'
  },
  init:function() {
    var self = this;
    self.$container = $(self.svg_container);
    self.$loading_template = horizon.networktopologyloader.setup_loader($(self.$container));

    if($('#networktopology').length === 0) {
      return;
    }
    self.color = d3.scale.category10();
    self.balloon_tmpl = '<span>balloon_tmpl</span>'
    self.balloon_device_tmpl = '<span>balloon_device_tmpl</span>'
    self.balloon_port_tmpl = '<span>balloon_port_tmpl</span>'

    $(document)
      .on('click', 'a.closeTopologyBalloon', function(e) {
        e.preventDefault();
        self.delete_balloon();
      })
      .on('click', '.topologyBalloon', function(e) {
        e.stopPropagation();
      })
      .on('click', 'a.vnc_window', function(e) {
        e.preventDefault();
        e.stopImmediatePropagation();
        var vnc_window = window.open($(this).attr('href'), vnc_window, 'width=760,height=560');
        self.delete_balloon();
      })
      .click(function(){
        self.delete_balloon();
      });

    $('.toggle-view > .btn').click(function(){
      self.draw_mode = $(this).data('value');
      $('g.network').remove();
      horizon.cookies.put('ntp_draw_mode',self.draw_mode);
      self.data_convert();
    });

    self.$loading_template.show();
    $('#networktopology').on('change', function() {
      self.load_network_info();
    });

    // register for message notifications
    //horizon.networktopologymessager.addMessageHandler(self.handleMessage, this);
  },

  /*handleMessage:function(message) {
    // noop
  },*/

  load_network_info:function(){
    var self = this;
    self.model = horizon.networktopologyloader.model;
    self.data_convert();
  },
  select_draw_mode:function() {
    var self = this;
    var draw_mode = 'normal';
    try {
      draw_mode = horizon.cookies.get('ntp_draw_mode');
    }
    catch(e) {
      // if the cookie does not exist, angular-cookie passes "undefined" to
      // JSON.parse which throws an exception
    }

    if (draw_mode && (draw_mode === 'normal' || draw_mode === 'small')) {
      self.draw_mode = draw_mode;
    } else {
      if (self.model.networks.length *
        self.element_properties.normal.network_width > $('#topologyCanvas').width()) {
        self.draw_mode = 'small';
      } else {
        self.draw_mode = 'normal';
      }
      horizon.cookies.put('ntp_draw_mode',self.draw_mode);
    }
    $('.toggle-view > .btn').each(function(){
      var $this = $(this);
      if($this.data('value') === self.draw_mode) {
        $this.addClass('active');
      } else {
        $this.removeClass('active');
      }
    });
  },
  data_convert:function() {
    var self = this;
    var model = self.model;
    $.each(model.networks, function(index, network) {
      self.network_index[network.id] = index;
    });
    self.select_draw_mode();
    var element_properties = self.element_properties[self.draw_mode];
    self.network_height = element_properties.top_margin;
    $.each([
      {model:model.routers, type:'router'},
      {model:model.servers, type:'instance'}
    ], function(index, devices) {
      var type = devices.type;
      var model = devices.model;
      $.each(model, function(index, device) {
        device.type = type;
        device.ports = self.select_port(device.id);
        var hasports = device.ports.length > 0;
        device.parent_network = (hasports) ? self.select_main_port(device.ports).network_id : self.model.networks[0].id;
        var height = element_properties.port_margin*(device.ports.length - 1);
        device.height = (self.draw_mode === 'normal' && height > element_properties.default_height) ? height : element_properties.default_height;
        device.pos_y = self.network_height;
        device.port_height = (self.draw_mode === 'small' && height > device.height) ? 1 : element_properties.port_height;
        device.port_margin = (self.draw_mode === 'small' && height > device.height) ? device.height/device.ports.length : element_properties.port_margin;
        self.network_height += device.height + element_properties.margin;
      });
    });
    $.each(model.networks, function(index, network) {
      network.devices = [];
      $.each([model.routers, model.servers],function(index, devices) {
        $.each(devices,function(index, device) {
          if(network.id === device.parent_network) {
            network.devices.push(device);
          }
        });
      });
    });
    self.network_height += element_properties.top_margin;
    self.network_height = (self.network_height > element_properties.network_min_height) ? self.network_height : element_properties.network_min_height;
    self.draw_topology();
    self.$loading_template.hide();
  },
  draw_topology:function() {
    var self = this;
    $(self.svg_container).removeClass('noinfo');
    if (self.model.networks.length <= 0) {
      $('g.network').remove();
      $(self.svg_container).addClass('noinfo');
      return;
    }
    var svg = d3.select(self.svg);
    var element_properties = self.element_properties[self.draw_mode];
    svg
      .attr('width',self.model.networks.length*element_properties.network_width)
      .attr('height',self.network_height);

    var network = svg.selectAll('g.network')
      .data(self.model.networks);

    network.enter()
      .append('g')
      .attr('class','network')
      .each(function(d){
        this.appendChild(d3.select(self.network_tmpl[self.draw_mode]).node().cloneNode(true));
        var $this = d3.select(this).select('.network-rect');
        if (d.url) {
          $this
            .on('mouseover',function(){
              $this.transition().style('fill', function() {
                return d3.rgb(self.get_network_color(d.id)).brighter(0.5);
              });
            })
            .on('mouseout',function(){
              $this.transition().style('fill', function() {
                return self.get_network_color(d.id);
              });
            })
            .on('click',function(){
              window.location.href = d.url;
            });
        } else {
          $this.classed('nourl', true);
        }
      });

    network
      .attr('id',function(d) { return 'id_' + d.id; })
      .attr('transform',function(d,i){
        return 'translate(' + element_properties.network_width * i + ',' + 0 + ')';
      })
      .select('.network-rect')
      .attr('height', function() { return self.network_height; })
      .style('fill', function(d) { return self.get_network_color(d.id); });
    network
      .select('.network-name')
      .attr('x', function() { return self.network_height/2; })
      .text(function(d) { return d.name; });
    network
      .select('.network-cidr')
      .attr('x', function(d) {
        var padding = isExternalNetwork(d) ? self.fa_globe_glyph_width : 0;
        return self.network_height - self.element_properties.cidr_margin -
          padding;
      })
      .text(function(d) {
        var cidr = $.map(d.subnets,function(n){
          return n.cidr;
        });
        return cidr.join(', ');
      });
    function isExternalNetwork(d) {
      return d['router:external'];
    }
    network
      .select('.network-type')
      .text(function(d) {
        return isExternalNetwork(d) ? self.fa_globe_glyph : '';
      })
      .attr('x', function() {
        return self.network_height - self.element_properties.cidr_margin;
      });

    $('[data-toggle="tooltip"]').tooltip({container: 'body'});

    network.exit().remove();

    var device = network.selectAll('g.device')
      .data(function(d) { return d.devices; });

    var device_enter = device.enter()
      .append("g")
      .attr('class','device')
      .each(function(d){
        var device_template = self[d.type + '_tmpl'][self.draw_mode];
        this.appendChild(d3.select(device_template).node().cloneNode(true));
      });

    device_enter
      .on('mouseenter',function(d){
      var $this = $(this);
      self.show_balloon(d,$this);
    })
      .on('click',function(){
        d3.event.stopPropagation();
      });

    device
      .attr('id',function(d) { return 'id_' + d.id; })
      .attr('transform',function(d){
        return 'translate(' + element_properties.device_x + ',' + d.pos_y + ')';
      })
      .select('.frame')
      .attr('height',function(d) { return d.height; });
    device
      .select('.texts_bg')
      .attr('y',function(d) {
        return element_properties.texts_bg_y + d.height - element_properties.default_height;
      });
    device
      .select('.type')
      .attr('y',function(d) {
        return element_properties.type_y + d.height - element_properties.default_height;
      });
    device
      .select('.name')
      .text(function(d) { return self.string_truncate(d.name); });
    device.each(function(d) {
      if (d.status === 'BUILD') {
        d3.select(this).classed('loading',true);
      } else if (d.task === 'deleting') {
        d3.select(this).classed('loading',true);
        if ('bl_' + d.id === self.balloon_id) {
          self.delete_balloon();
        }
      } else {
        d3.select(this).classed('loading',false);
        if ('bl_' + d.id === self.balloon_id) {
          var $this = $(this);
          self.show_balloon(d,$this);
        }
      }
    });

    device.exit().each(function(d){
      if ('bl_' + d.id === self.balloon_id) {
        self.delete_balloon();
      }
    }).remove();

    var port = device.select('g.ports')
      .selectAll('g.port')
      .data(function(d) { return d.ports; });

    var port_enter = port.enter()
      .append('g')
      .attr('class','port')
      .attr('id',function(d) { return 'id_' + d.id; });

    port_enter
      .append('line')
      .attr('class','port_line');

    port_enter
      .append('text')
      .attr('class','port_text');

    device.select('g.ports').each(function(d){
      this._portdata = {};
      this._portdata.ports_length = d.ports.length;
      this._portdata.parent_network = d.parent_network;
      this._portdata.device_height = d.height;
      this._portdata.port_height = d.port_height;
      this._portdata.port_margin = d.port_margin;
      this._portdata.left = 0;
      this._portdata.right = 0;
      $(this).mouseenter(function(e){
        e.stopPropagation();
      });
    });

    port.each(function(d){
      var index_diff = self.get_network_index(this.parentNode._portdata.parent_network) -
        self.get_network_index(d.network_id);
      this._index_diff = index_diff = (index_diff >= 0)? ++index_diff : index_diff;
      this._direction = (this._index_diff < 0)? 'right' : 'left';
      this._index = this.parentNode._portdata[this._direction] ++;

    });

    port.attr('transform',function(){
      var x = (this._direction === 'left') ? 0 : element_properties.device_width;
      var ports_length = this.parentNode._portdata[this._direction];
      var distance = this.parentNode._portdata.port_margin;
      var y = (this.parentNode._portdata.device_height -
        (ports_length -1)*distance)/2 + this._index*distance;
      return 'translate(' + x + ',' + y + ')';
    });

    port
      .select('.port_line')
      .attr('stroke-width',function() {
        return this.parentNode.parentNode._portdata.port_height;
      })
      .attr('stroke', function(d) {
        return self.get_network_color(d.network_id);
      })
      .attr('x1',0).attr('y1',0).attr('y2',0)
      .attr('x2',function() {
        var parent = this.parentNode;
        var width = (Math.abs(parent._index_diff) - 1)*element_properties.network_width +
          element_properties.port_width;
        return (parent._direction === 'left') ? -1*width : width;
      });

    port
      .select('.port_text')
      .attr('x',function() {
        var parent = this.parentNode;
        if (parent._direction === 'left') {
          d3.select(this).classed('left',true);
          return element_properties.port_text_margin.x*-1;
        } else {
          d3.select(this).classed('left',false);
          return element_properties.port_text_margin.x;
        }
      })
      .attr('y',function() {
        return element_properties.port_text_margin.y;
      })
      .text(function(d) {
        var ip_label = [];
        $.each(d.fixed_ips, function() {
          ip_label.push(this.ip_address);
        });
        return ip_label.join(',');
      });

    port.exit().remove();
  },
  get_network_color: function(network_id) {
    return this.color(this.get_network_index(network_id));
  },
  get_network_index: function(network_id) {
    return this.network_index[network_id];
  },
  select_port: function(device_id){
    return $.map(this.model.ports,function(port){
      if (port.device_id === device_id) {
        return port;
      }
    });
  },
  select_main_port: function(ports){
    var _self = this;
    var main_port_index = 0;
    var MAX_INT = 4294967295;
    var min_port_length = MAX_INT;
    $.each(ports, function(index, port){
      var port_length = _self.sum_port_length(port.network_id, ports);
      if(port_length < min_port_length){
        min_port_length = port_length;
        main_port_index = index;
      }
    });
    return ports[main_port_index];
  },
  sum_port_length: function(network_id, ports){
    var self = this;
    var sum_port_length = 0;
    var base_index = self.get_network_index(network_id);
    $.each(ports, function(index, port){
      sum_port_length += base_index - self.get_network_index(port.network_id);
    });
    return sum_port_length;
  },
  string_truncate: function(string) {
    var self = this;
    var str = string;
    var max_size = self.element_properties.device_name_max_size;
    var suffix = self.element_properties.device_name_suffix;
    var bytes = 0;
    for (var i = 0; i < str.length; i++) {
      bytes += str.charCodeAt(i) <= 255 ? 1 : 2;
      if (bytes > max_size) {
        str = str.substr(0, i) + suffix;
        break;
      }
    }
    return str;
  },
  delete_device: function(device_type, device_id) {
    var message = {id:device_id};
    var target = device_type === 'instance' ? 'instance?id=' + device_id : device_type;
    horizon.networktopologymessager.post_message(device_id, target, message, device_type, 'delete', data={});
  },
  delete_port: function(router_id, port_id, network_id) {
    var message = {id:port_id};
    var data = {router_id: router_id, network_id: network_id};
    horizon.networktopologymessager.post_message(port_id, 'router/' + router_id + '/', message, 'port', 'delete', data);
  },
  show_balloon:function(d,element) {
    var self = this;
    var element_properties = self.element_properties[self.draw_mode];
    if (self.balloon_id) {
      self.delete_balloon();
    }
    var balloon_tmpl = self.balloon_tmpl;
    var device_tmpl = self.balloon_device_tmpl;
    var port_tmpl = self.balloon_port_tmpl;
    var balloon_id = 'bl_' + d.id;
    var ports = [];
    $.each(d.ports,function(i, port){
      var object = {};
      object.id = port.id;
      object.router_id = port.device_id;
      object.url = port.url;
      object.port_status = port.status;
      object.port_status_class = (port.original_status === "ACTIVE")? 'active' : 'down';
      var ip_address = '';
      try {
        ip_address = port.fixed_ips[0].ip_address;
      }catch(e){
        ip_address = gettext('None');
      }
      var device_owner = '';
      try {
        device_owner = port.device_owner.replace('network:','');
      }catch(e){
        device_owner = gettext('None');
      }
      var network_id = '';
      try {
        network_id = port.network_id;
      }catch(e) {
        network_id = gettext('None');
      }
      object.network_id = network_id;
      object.ip_address = ip_address;
      object.device_owner = device_owner;
      object.is_interface = (
        device_owner === 'router_interface' ||
        device_owner === 'router_gateway' ||
        device_owner === 'ha_router_replicated_interface'
      );
      // (device_owner === 'router_interface' || device_owner === 'router_gateway');
      ports.push(object);
    });
    var html;
    var html_data = {
      balloon_id:balloon_id,
      id:d.id,
      url:d.url,
      name:d.name,
      type:d.type,
      delete_label: gettext("Delete"),
      status:d.status,
      status_class:(d.original_status === "ACTIVE")? 'active' : 'down',
      status_label: gettext("STATUS"),
      id_label: gettext("ID"),
      interfaces_label: gettext("Interfaces"),
      delete_interface_label: gettext("Delete Interface"),
      open_console_label: gettext("Open Console"),
      view_details_label: gettext("View Details")
    };
    if (d.type === 'router') {
      html_data.delete_label = gettext("Delete Router");
      html_data.view_details_label = gettext("View Router Details");
      html_data.port = ports;
      html_data.add_interface_url = d.url + 'addinterface';
      html_data.add_interface_label = gettext("Add Interface");
      html = balloon_tmpl.render(html_data,{
        table1:device_tmpl,
        table2:(ports.length > 0) ? port_tmpl : null
      });
    } else if (d.type === 'instance') {
      html_data.delete_label = gettext("Delete Instance");
      html_data.view_details_label = gettext("View Instance Details");
      html_data.console_id = d.id;
      html_data.console = d.console;
      html = balloon_tmpl.render(html_data,{
        table1:device_tmpl
      });
    } else {
      return;
    }
    $(self.svg_container).append(html);
    var device_position = element.find('.frame');
    var sidebar_width = $("#sidebar").width();
    var navbar_height = $(".navbar").height();
    var breadcrumb_height = $(".breadcrumb").outerHeight(true);
    var pageheader_height = $(".page-header").outerHeight(true);
    var launchbuttons_height = $(".launchButtons").height();
    var height_offset = navbar_height + breadcrumb_height + pageheader_height + launchbuttons_height;
    var device_offset = device_position.offset();
    var x = Math.round(device_offset.left + element_properties.device_width + element_properties.balloon_margin.x - sidebar_width);
    // 15 is magic pixel value that seems to make things line up
    var y = Math.round(device_offset.top + element_properties.balloon_margin.y - height_offset + 15);
    var $balloon = $('#' + balloon_id);
    $balloon.css({
      'left': '0px',
      'top': y + 'px'
    });
    var balloon_width = $balloon.outerWidth();
    var left_x = device_offset.left - balloon_width - element_properties.balloon_margin.x - sidebar_width;
    var right_x = x + balloon_width + element_properties.balloon_margin.x + sidebar_width;

    if (left_x > 0 && right_x > $(window).outerWidth()) {
      x = left_x;
      $balloon.addClass('leftPosition');
    }
    $balloon.css({
      'left': x + 'px'
    }).show();

    $balloon.find('.delete-device').click(function(){
      var $this = $(this);
      var delete_modal = horizon.datatables.confirm($this);
      delete_modal.find('.btn-primary').click(function () {
        $this.prop('disabled', true);
        d3.select('#id_' + $this.data('device-id')).classed('loading',true);
        self.delete_device($this.data('type'),$this.data('device-id'));
        horizon.modals.spinner.modal('hide');
      });
    });
    $balloon.find('.delete-port').click(function(){
      var $this = $(this);
      var delete_modal = horizon.datatables.confirm($this);
      delete_modal.find('.btn-primary').click(function () {
        $this.prop('disabled', true);
        self.delete_port($this.data('router-id'),$this.data('port-id'),$this.data('network-id'));
        horizon.modals.spinner.modal('hide');
      });
    });
    self.balloon_id = balloon_id;
  },
  delete_balloon:function() {
    var self = this;
    if(self.balloon_id) {
      $('#' + self.balloon_id).remove();
      self.balloon_id = null;
    }
  }
};
