/*
 * Created on Apr 30, 2004
 * Created by Alon Rohter
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 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.
 *
 */

package com.aelitis.azureus.core.peermanager.messaging.azureus;

import java.util.*;

import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.util.*;

import com.aelitis.azureus.core.peermanager.messaging.*;
import com.aelitis.azureus.core.peermanager.peerdb.*;




/**
 * AZ peer exchange message.
 */
public class AZPeerExchange implements AZMessage, AZStylePeerExchange {
  private static final LogIDs LOGID = LogIDs.NET;

  private static final byte bss = DirectByteBuffer.SS_MSG;

  private DirectByteBuffer buffer = null;
  private String description = null;
  
  private final byte version;
  private final byte[] infohash;
  private final PeerItem[] peers_added;
  private final PeerItem[] peers_dropped;
  
  
  
  public AZPeerExchange( byte[] _infohash, PeerItem[] _peers_added, PeerItem[] _peers_dropped, byte version ) {
    this.infohash = _infohash;
    this.peers_added = _peers_added;
    this.peers_dropped = _peers_dropped;
    this.version = version;
  }
  

  
  private void insertPeers( String key_name, Map root_map, PeerItem[] peers ) {
    if( peers != null && peers.length > 0 ) {
      ArrayList raw_peers = new ArrayList();
      byte[] handshake_types = new byte[ peers.length ];
      byte[] udp_ports = new byte[peers.length*2];	// 2403 B55+
      int	num_valid_udp = 0;
      
      for( int i=0; i < peers.length; i++ ) {
        raw_peers.add( peers[i].getSerialization() );
        handshake_types[i] = (byte)peers[i].getHandshakeType();
        int	udp_port = peers[i].getUDPPort();
        if ( udp_port > 0 ){
        	num_valid_udp++;
           	udp_ports[i*2] = (byte)(udp_port>>8);
           	udp_ports[i*2+1] = (byte)udp_port;
        }
      }

      root_map.put( key_name, raw_peers );
      root_map.put( key_name + "_HST", handshake_types );
      if ( num_valid_udp > 0 ){
    	root_map.put( key_name + "_UDP", udp_ports );     
      }
    }
  }
  
  
  
  private PeerItem[] extractPeers( String key_name, Map root_map ) {
    PeerItem[] return_peers = null;
    ArrayList peers = new ArrayList();

    List raw_peers = (List)root_map.get( key_name );
    if( raw_peers != null ) {
      int	peer_num = raw_peers.size();
      byte[] handshake_types = (byte[])root_map.get( key_name + "_HST" );
      byte[] udp_ports = (byte[])root_map.get( key_name + "_UDP" ); // 2403 B55+
      int pos = 0;
    
      if ( handshake_types != null && handshake_types.length != peer_num ){
     	  Logger.log(new LogEvent( LOGID, LogEvent.LT_WARNING,"PEX: invalid handshake types received: peers=" + peer_num + ",handshakes=" + handshake_types.length ));
       	  handshake_types = null;
      }
      
      if ( udp_ports != null && udp_ports.length != peer_num*2 ){
       	  Logger.log(new LogEvent( LOGID, LogEvent.LT_WARNING,"PEX: invalid udp ports received: peers=" + peer_num + ",udp_ports=" + udp_ports.length ));
    	  udp_ports = null;
      }
      
      for( Iterator it = raw_peers.iterator(); it.hasNext(); ) {
        byte[] full_address = (byte[])it.next();
        
        byte type = PeerItemFactory.HANDSHAKE_TYPE_PLAIN;        
        if( handshake_types != null ) { //only 2307+ send types
        	type = handshake_types[pos];
        }
        int	udp_port = 0;
        if ( udp_ports != null ){
        	udp_port = ((udp_ports[pos*2]<<8)&0xff00) + (udp_ports[pos*2+1]&0xff);
        }
        try{
        	PeerItem peer = PeerItemFactory.createPeerItem( full_address, PeerItemFactory.PEER_SOURCE_PEER_EXCHANGE, type, udp_port, AENetworkClassifier.AT_PUBLIC );
        	peers.add( peer );
        }catch( Exception t ){
            Logger.log(new LogEvent( LOGID, LogEvent.LT_WARNING,"PEX: invalid peer received" ));	 
        }
        pos++;
      }
    }
    
    if( !peers.isEmpty() ) {
      return_peers = new PeerItem[ peers.size() ];
      peers.toArray( return_peers );
    }
    
    return return_peers;
  }
  
  
  
  
  
  public byte[] getInfoHash() {  return infohash;  }
  
  public PeerItem[] getAddedPeers() {  return peers_added;  }
  
  public PeerItem[] getDroppedPeers() {  return peers_dropped;  }
  
  
    
  public String getID() {  return AZMessage.ID_AZ_PEER_EXCHANGE;  }
  public byte[] getIDBytes() {  return AZMessage.ID_AZ_PEER_EXCHANGE_BYTES;  }
  
  public String getFeatureID() {  return AZMessage.AZ_FEATURE_ID;  }  
  
  public int getFeatureSubID() { return AZMessage.SUBID_AZ_PEER_EXCHANGE;  }
  
  public int getType() {  return Message.TYPE_PROTOCOL_PAYLOAD;  }
   
  public byte getVersion() { return version; };
  
  public String getDescription() {
    if( description == null ) {
      int add_count = peers_added == null ? 0 : peers_added.length;
      int drop_count = peers_dropped == null ? 0 : peers_dropped.length;
      
      description = getID()+ " for infohash " +ByteFormatter.nicePrint( infohash, true )+ " with " +add_count+ " added and " +drop_count+ " dropped peers";
    }
    
    return description;
  }
  
  
  public DirectByteBuffer[] getData() {
    if( buffer == null ) {
      Map payload_map = new HashMap();
      
      payload_map.put( "infohash", infohash );
      insertPeers( "added", payload_map, peers_added );
      insertPeers( "dropped", payload_map, peers_dropped );

      buffer = MessagingUtil.convertPayloadToBencodedByteStream( payload_map, DirectByteBuffer.AL_MSG_AZ_PEX );

      if( buffer.remaining( bss ) > 2000 )  System.out.println( "Generated AZPeerExchange size = " +buffer.remaining( bss )+ " bytes" );
    }
    
    return new DirectByteBuffer[]{ buffer };
  }
  
  
  public Message deserialize( DirectByteBuffer data, byte version ) throws MessageException {
    if( data.remaining( bss ) > 2000 )  System.out.println( "Received PEX msg byte size = " +data.remaining( bss ) );
    
    Map root = MessagingUtil.convertBencodedByteStreamToPayload( data, 10, getID() );

    byte[] hash = (byte[])root.get( "infohash" );
    if( hash == null )  throw new MessageException( "hash == null" );
    if( hash.length != 20 )  throw new MessageException( "hash.length != 20: " +hash.length );

    PeerItem[] added = extractPeers( "added", root );
    PeerItem[] dropped = extractPeers( "dropped", root );
      
    if( added == null && dropped == null )  throw new MessageException( "[" +getID()+ "] received exchange message without any adds or drops" );

    return new AZPeerExchange( hash, added, dropped, version );
  }
  
  
  public void destroy() {
    if( buffer != null )  buffer.returnToPool();
  }
  
  public int getMaxAllowedPeersPerVolley(boolean initial, boolean added) {
	  return PeerExchangerItem.MAX_PEERS_PER_VOLLEY;
  }
  
}
