/*
 * Author: Yasuhito Takamiya <yasuhito@gmail.com>
 *
 * Copyright (C) 2008-2012 NEC Corporation
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include "trema.h"
#include "ruby.h"


extern VALUE mTrema;
VALUE cPort;


/*
 * Creates a {Port} instance that encapsulates the properties of a physical port.
 * The newly-created instance is initialized from an options hash.
 *
 * @overload initialize(options={})
 *
 *   @example
 *     Port.new(
 *       :number => 1,
 *       :hw_addr => Mac.new( "4e:1e:9a:7a:44:be" ),
 *       :name => "trema0-0",
 *       :config => 0,
 *       :state => 0,
 *       :curr => 192,
 *       :advertised => 0,
 *       :supported => 0,
 *       :peer => 0
 *     )
 *
 *   @param [Hash] options the options hash.
 *
 *   @option options [Number] :number
 *     the port's unique number.
 *
 *   @option options [Mac] :hw_addr
 *     the port's Ethernet address expressed as a {Mac} object.
 *
 *   @option options [String] :name
 *     the port's human readable defined name.
 *
 *   @option options [Number] :config
 *     the port's configuration as a 32-bit bitmap.
 *
 *   @option options [Number] :state
 *     the port's state as a 32-bit bitmap.
 *
 *   @option options [Number] :curr
 *      the port's current features as a 32-bit bitmap.
 *
 *   @option options [Number] :advertised
 *     the port's advertised features as a 32-bit bitmap.
 *
 *   @option options [Number] :supported
 *     the port's supported features as a 32-bit bitmap.
 *
 *   @option options [Number] :peer
 *     the features advertised by the peer connected to the port as a 32-bit bitmap.
 *
 *   @return [Port]
 *     an object that encapsulates the properties of a physical port.
 */
static VALUE
port_init( VALUE self, VALUE options ) {
  VALUE number = rb_hash_aref( options, ID2SYM( rb_intern( "number" ) ) );
  rb_iv_set( self, "@number", number );

  VALUE hw_addr = rb_hash_aref( options, ID2SYM( rb_intern( "hw_addr" ) ) );
  rb_iv_set( self, "@hw_addr", hw_addr );

  VALUE name = rb_hash_aref( options, ID2SYM( rb_intern( "name" ) ) );
  rb_iv_set( self, "@name", name );
  
  VALUE config = rb_hash_aref( options, ID2SYM( rb_intern( "config" ) ) );
  rb_iv_set( self, "@config", config );

  VALUE state = rb_hash_aref( options, ID2SYM( rb_intern( "state" ) ) );
  rb_iv_set( self, "@state", state );

  VALUE curr = rb_hash_aref( options, ID2SYM( rb_intern( "curr" ) ) );
  rb_iv_set( self, "@curr", curr );

  VALUE advertised = rb_hash_aref( options, ID2SYM( rb_intern( "advertised" ) ) );
  rb_iv_set( self, "@advertised", advertised );

  VALUE supported = rb_hash_aref( options, ID2SYM( rb_intern( "supported" ) ) );
  rb_iv_set( self, "@supported", supported );

  VALUE peer = rb_hash_aref( options, ID2SYM( rb_intern( "peer" ) ) );
  rb_iv_set( self, "@peer", peer );
  return self;
}


/*
 * The port's unique number.
 *
 * @return [Number] the value of number.
 */
static VALUE
port_number( VALUE self ) {
  return rb_iv_get( self, "@number" );
}


/*
 * The port's Ethernet address expressed as a {Mac} object.
 *
 * @return [Mac] the value of hw_addr.
 */
static VALUE
port_hw_addr( VALUE self ) {
  return rb_iv_get( self, "@hw_addr" );
}


/*
 * The port's human readable defined name.
 *
 * @return [String] the value of name.
 */
static VALUE
port_name( VALUE self ) {
  return rb_iv_get( self, "@name" );
}


/*
 * The port's configuration as a 32-bit bitmap.
 *
 * @return [Number] the value of config.
 */
static VALUE
port_config( VALUE self ) {
  return rb_iv_get( self, "@config" );
}


/*
 * The port's state as a 32-bit bitmap.
 *
 * @return [Number] the value of state.
 */
static VALUE
port_state( VALUE self ) {
  return rb_iv_get( self, "@state" );
}


/*
 * The port's current features as a 32-bit bitmap.
 *
 * @return [Number] the value of curr.
 */
static VALUE
port_curr( VALUE self ) {
  return rb_iv_get( self, "@curr" );
}


/*
 * The port's advertised features as a 32-bit bitmap.
 *
 * @return [Number] the value of advertised.
 */
static VALUE
port_advertised( VALUE self ) {
  return rb_iv_get( self, "@advertised" );
}


/*
 * The port's supported features as a 32-bit bitmap.
 *
 * @return [Number] the value of supported.
 */
static VALUE 
port_supported( VALUE self ) {
  return rb_iv_get( self, "@supported" );
}


/*
 * The features advertised by the peer connected to the port as a 32-bit bitmap.
 *
 * @return [Number] the value of peer.
 */
static VALUE
port_peer( VALUE self ) {
  return rb_iv_get( self, "@peer" );
}


/*
 * Tests if the port is up.
 *
 * @return [Boolean] true if port is up otherwise false.
 */
static VALUE
port_up( VALUE self ) {
  uint32_t config = ( uint16_t ) NUM2UINT( rb_iv_get( self, "@config" ) );
  if ( ( config & OFPPC_PORT_DOWN ) == OFPPC_PORT_DOWN ) {
    return Qfalse;
  }
  uint32_t state = ( uint16_t ) NUM2UINT( rb_iv_get( self, "@state" ) );
  if ( ( state & OFPPS_LINK_DOWN ) == OFPPS_LINK_DOWN ) {
    return Qfalse;
  }
  return Qtrue;
}


/*
 * Tests if the port is down.
 *
 * @return [Boolean] true if port is down otherwise false.
 */
static VALUE
port_down( VALUE self ) {
  return port_up( self ) == Qfalse ? Qtrue : Qfalse;
}


/*
 * Compares two ports by substracting their unique numbers.
 *
 * @return [Number] the result of the substraction. Zero ports are equal.
 */
static VALUE
port_compare( VALUE self, VALUE other ) {
  uint16_t a = ( uint16_t ) NUM2UINT( rb_iv_get( self, "@number" ) );
  uint16_t b = ( uint16_t ) NUM2UINT( rb_iv_get( other, "@number" ) );
  return UINT2NUM( ( uint16_t ) ( a - b ) );
}


void
Init_port() {
  cPort = rb_define_class_under( mTrema, "Port", rb_cObject );
  rb_define_method( cPort, "initialize", port_init, 1 );
  rb_define_method( cPort, "number", port_number, 0 );
  rb_define_method( cPort, "hw_addr", port_hw_addr, 0 );
  rb_define_method( cPort, "name", port_name, 0 );
  rb_define_method( cPort, "config", port_config, 0 );
  rb_define_method( cPort, "state", port_state, 0 );
  rb_define_method( cPort, "curr", port_curr, 0 );
  rb_define_method( cPort, "advertised", port_advertised, 0 );
  rb_define_method( cPort, "supported", port_supported, 0 );
  rb_define_method( cPort, "peer", port_peer, 0 );
  rb_define_method( cPort, "up?", port_up, 0 );
  rb_define_method( cPort, "down?", port_down, 0 );
  rb_define_method( cPort, "<=>", port_compare, 1 );
}


VALUE
port_from( const struct ofp_phy_port *phy_port ) {
  VALUE attributes = rb_hash_new();
  rb_hash_aset( attributes, ID2SYM( rb_intern( "number" ) ), UINT2NUM( phy_port->port_no ) );
  VALUE hw_addr = rb_funcall( rb_eval_string( "Trema::Mac" ), rb_intern( "new" ), 1, ULL2NUM( mac_to_uint64( phy_port->hw_addr ) ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "hw_addr" ) ), hw_addr );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "name" ) ), rb_str_new2( phy_port->name ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "config" ) ), UINT2NUM( phy_port->config ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "state" ) ), UINT2NUM( phy_port->state ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "curr" ) ), UINT2NUM( phy_port->curr ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "advertised" ) ), UINT2NUM( phy_port->advertised ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "supported" ) ), UINT2NUM( phy_port->supported ) );
  rb_hash_aset( attributes, ID2SYM( rb_intern( "peer" ) ), UINT2NUM( phy_port->peer ) );
  return rb_funcall( cPort, rb_intern( "new" ), 1, attributes );
}


/*
 * Local variables:
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * End:
 */