/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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. 
 * 
 */
package org.apache.directory.server.dhcp.messages;


import java.text.ParseException;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.directory.server.i18n.I18n;


/**
 * A representation of a DHCP hardware address.
 * 
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public final class HardwareAddress
{
    /**
     * [htype] Hardware address type, see ARP section in "Assigned Numbers" RFC;
     * e.g., '1' = 10mb ethernet.
     */
    private final short type;

    /**
     * [hlen] Hardware address length (e.g. '6' for 10mb ethernet).
     */
    private final short length;

    /**
     * [chaddr] Client hardware address.
     */
    private final byte[] address;


    /**
     * @param type
     * @param length
     * @param address
     */
    public HardwareAddress( short type, short length, byte[] address )
    {
        this.type = type;
        this.length = length;
        this.address = address;
    }


    public byte[] getAddress()
    {
        return address;
    }


    public short getLength()
    {
        return length;
    }


    public short getType()
    {
        return type;
    }


    /**
     * @see java.lang.Object#hashCode()
     * @return the instance's hash code 
     */
    public int hashCode()
    {
        int hashCode = 98643532 ^ type ^ length;

        for ( int i = 0; i < length; i++ )
        {
            hashCode ^= address[i];
        }

        return hashCode;
    }


    /*
     * @see java.lang.Object#equals(java.lang.Object)
     */
    public boolean equals( Object obj )
    {
        if ( null == obj || !( obj.getClass().equals( HardwareAddress.class ) ) )
        {
            return false;
        }

        HardwareAddress hw = ( HardwareAddress ) obj;

        return length == hw.length && type == hw.type && Arrays.equals( address, hw.address );
    }


    /**
     * Create the string representation of the hardware address native to the
     * corresponding address type. This method currently supports only type
     * 1==ethernet with the representation <code>a1:a2:a3:a4:a5:a6</code>.<br>
     * For all other types, this method falls back to the representation created
     * by toString().
     * 
     * @see java.lang.Object#toString()
     */
    public String getNativeRepresentation()
    {
        StringBuffer sb = new StringBuffer();

        switch ( type )
        {
            case 1:
                for ( int i = 0; i < length; i++ )
                {
                    if ( i > 0 )
                    {
                        sb.append( ":" );
                    }

                    String hex = Integer.toHexString( address[i] & 0xff );

                    if ( hex.length() < 2 )
                    {
                        sb.append( '0' );
                    }

                    sb.append( hex );
                }

                break;

            default:
                sb.append( toString() );
        }

        return sb.toString();
    }


    /**
     * Create a string representation of the hardware address. The string
     * representation is in the format<br>
     * <code>t/a1:a2:a3...</code><br>
     * Where <code>t</code> represents the address type (decimal) and
     * <code>a<sub>n</sub></code> represent the address bytes (hexadecimal).
     * 
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        sb.append( type );
        sb.append( "/" );

        for ( int i = 0; i < length; i++ )
        {
            if ( i > 0 )
            {
                sb.append( ":" );
            }

            String hex = Integer.toHexString( address[i] & 0xff );

            if ( hex.length() < 2 )
            {
                sb.append( '0' );
            }

            sb.append( hex );
        }

        return sb.toString();
    }

    private static final Pattern PARSE_PATTERN = Pattern
        .compile( "(\\d+)\\s+(?:(\\p{XDigit}{1,2}):)*(\\p{XDigit}{1,2})?" );


    /**
     * Parses a string representation of a hardware address according to the
     * specification given in {@link #toString()}.
     * 
     * @param s
     * @return HardwareAddress
     * @throws ParseException
     */
    public static HardwareAddress valueOf( String s )
    {
        if ( null == s || s.length() == 0 )
        {
            return null;
        }

        Matcher m = PARSE_PATTERN.matcher( s );

        if ( !m.matches() )
        {
            throw new IllegalArgumentException( I18n.err( I18n.ERR_637, s ) );
        }

        int type = Integer.parseInt( m.group( 1 ) );
        int len = m.groupCount() - 1;

        byte addr[] = new byte[len];

        for ( int i = 0; i < addr.length; i++ )
        {
            addr[i] = ( byte ) Integer.parseInt( m.group( i + 2 ), 16 );
        }

        return new HardwareAddress( ( short ) type, ( short ) len, addr );
    }
}
