/*
 *  PHEX - The pure-java Gnutella-servent.
 *  Copyright (C) 2001 - 2006 Phex Development Group
 *
 *  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
 * 
 *  Created on 04.01.2006
 *  --- CVS Information ---
 *  $Id: CapabilitiesVMsg.java 3651 2006-12-31 13:29:54Z GregorK $
 */
package phex.msg.vendor;

import java.io.*;
import java.util.*;

import phex.msg.InvalidMessageException;
import phex.msg.MsgHeader;
import phex.utils.IOUtil;
import phex.utils.NLogger;
import phex.utils.NLoggerNames;

/**
 * A message to tell which capabilities are supported.
 */
public class CapabilitiesVMsg extends VendorMsg
{
    private static final int VERSION = 0;
    
    /**
     * Feature search bytes. Represent 'WHAT'
     */
    private static final byte[] FEATURE_SEARCH_BYTES = {(byte)87, (byte)72,
                                                        (byte)65, (byte)84};
    private static final int FEATURE_SEARCH_VERSION = 1;
    
    private static CapabilitiesVMsg myCapavilitiesVMsg;
    
    /**
     * Supported capabilities.
     */
    private final Set<SupportedCapability> capabilitiesSet;
    
    /**
     * @param header
     * @param version
     * @param data
     */
    public CapabilitiesVMsg( MsgHeader header, byte[] vendorId,
        int subSelector, int version, byte[] data)
        throws InvalidMessageException
    {
        super(header, vendorId, subSelector, version, data);
        if (version > VERSION)
        {
            throw new InvalidMessageException(
                "Vendor Message 'MessagesSupported' with invalid version: "
                    + version);
        }
        capabilitiesSet = new HashSet<SupportedCapability>();
        try
        {
            ByteArrayInputStream dataStream = new ByteArrayInputStream( data );
            int itemCount = IOUtil.unsignedShort2Int( IOUtil.deserializeShortLE( dataStream ) );
            byte[] itemBytes = new byte[6];
            for (int i = 0; i < itemCount; i++)
            {
                dataStream.read(itemBytes);
                SupportedCapability supportedCapability = new SupportedCapability(itemBytes);
                capabilitiesSet.add( supportedCapability );
            }
        } 
        catch ( IOException exp )
        {
            NLogger.error( NLoggerNames.MESSAGE_ENCODE_DECODE, exp, exp );
            throw new InvalidMessageException(exp.getMessage());
        }
    }
    
    /**
     * Create my MessagesSupportedVMsg used to tell others.
     */
    private CapabilitiesVMsg()
    {
        super(VENDORID_NULL, SUBSELECTOR_CAPABILITIES, VERSION, 
            IOUtil.EMPTY_BYTE_ARRAY );
        capabilitiesSet = new HashSet<SupportedCapability>();
        createCapabilitiesMsgData( );
    }
    
    public boolean isCapabilitySupported( byte[] capabilityName )
    {
        Iterator iter = capabilitiesSet.iterator();
        while ( iter.hasNext() )
        {
            SupportedCapability cap = (SupportedCapability) iter.next();
            if (Arrays.equals(capabilityName, cap.getName() ) )
            {
                return true;
            }            
        }
        return false;
    }
    
    public boolean isFeatureSearchSupported() 
    {
        return isCapabilitySupported(FEATURE_SEARCH_BYTES);
    }
    
    public static CapabilitiesVMsg getMyCapabilitiesVMsg()
    {
        if ( myCapavilitiesVMsg == null )
        {
            myCapavilitiesVMsg = new CapabilitiesVMsg();
        }
        return myCapavilitiesVMsg;
    }
    
    private void createCapabilitiesMsgData( )
    {
        SupportedCapability cap = null;
        cap = new SupportedCapability(FEATURE_SEARCH_BYTES, 
            FEATURE_SEARCH_VERSION);
        capabilitiesSet.add( cap );
        
        try 
        {
            ByteArrayOutputStream outStream = new ByteArrayOutputStream();
            IOUtil.serializeShortLE( (short)capabilitiesSet.size(), outStream );
            
            Iterator iterator = capabilitiesSet.iterator();
            while ( iterator.hasNext() ) 
            {
                cap = (SupportedCapability)iterator.next();
                cap.serialize( outStream );
            }
            byte[] data = outStream.toByteArray();
            setVenderMsgData( data );
        }
        catch (IOException exp)
        {
            // should never happen
            NLogger.error( NLoggerNames.MESSAGE_ENCODE_DECODE, exp, exp );
        }
    }
    
    /** 
     * Holds single capability.
     */  
    private static class SupportedCapability 
    {
        private byte[] name;
        private int version;
        private int hashCode = -1;
        
        public SupportedCapability(byte[] name, int version) 
        {
            this.name = name;
            this.version = version;
        }

        /**
         * Constructs a new SupportedMessageBlock with data from the 
         * InputStream.  If not enough data is available,
         * throws BadPacketException.
         */
        public SupportedCapability( byte[] itemBytes )
            throws InvalidMessageException
        {
            if ( itemBytes.length < 6)
            {
                throw new InvalidMessageException( "Invalid capability data.");
            }
            
            // first 4 bytes are capability name
            name = new byte[4];
            System.arraycopy(itemBytes, 0, name, 0, 4);
            version = IOUtil.unsignedShort2Int( IOUtil.deserializeShortLE(
                itemBytes, 4 ) );
        }
        
        public void serialize( OutputStream outStream ) 
            throws IOException
        {
            outStream.write( name );
            IOUtil.serializeShortLE((short)version, outStream);
        }
        
        public byte[] getName()
        {
            return name;
        }

        public boolean equals(Object obj)
        {
            if (obj instanceof SupportedCapability) 
            {
                SupportedCapability cap = (SupportedCapability) obj;
                return version == cap.version && Arrays.equals( name, cap.name );
            }
            return false;
        }

        public int hashCode()
        {
            if ( hashCode == -1 )
            {
                int code = 37*version;
                for (int i = 0; i < name.length; i++)
                {
                    code += 37*name[i];
                }
                hashCode = code;
            }
            return hashCode;
        }
        
        public String toString() 
        {
            return new String(name) + "/" + version;
        }
    }
}


