/*
 *
 *
 * Copyright  1990-2009 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, 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 version 2 for more details (a copy is
 * included at /legal/license.txt).
 *
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions.
 */
package com.sun.jsr082.bluetooth;

import javax.bluetooth.RemoteDevice;
import javax.bluetooth.UUID;
import javax.bluetooth.DataElement;

import com.sun.jsr082.bluetooth.SDPClient;

import java.util.Enumeration;
import java.util.Hashtable;

/*
 * This class saves information about every service descovery request
 * to provide functionality of DiscoveryAgent using multiple requests
 * via SDPClient (Service Descovery Protocol) by ServiceSelector
 * and ServiceSearcher classes.
 */
abstract public class ServiceSearcherBase implements SDPResponseListener {

    /*
     * maximum number of allowed UUIDS in search uuids sequence
     */
    private static final int MAX_ALLOWED_UUIDS = 12;
    private static final Object FAKE_VALUE = new Object();
    /* Mask to determine an attribute ID out of range. */
    private static final int MASK_OVERFLOW = 0xffff0000;

    /* RemoteDevice whose response to be listened. */
    RemoteDevice btDev;

    /*
     * The UUIDs from SDP_ServiceSearchRequest or
     * SDP_ServiceSearchAttrbuteRequest.
     *
     * @see SDPClient#serviceSearchRequest
     * @see SDPClient#serviceSearchAttributeRequest
     */
    UUID[] uuidSet;

    /*
     * Attributes list from SDP_ServiceSearchAttrbuteRequest.
     *
     * @see SDPClient#serviceSearchAttributeRequest
     */
    int[] attrSet;

    /*
     * Creates ServiceSearcherBase and save all required info in it.
     */
    ServiceSearcherBase() {
        super();
    }

    /*
     * Creates an instance of ServiceSearcherBase
     *
     * @param attrSet list of attributes whose values are requested.
     * @param uuidSet list of UUIDs that indicate services relevant to request.
     * @param btDev remote Bluetooth device to listen response from.
     * @param discListener discovery listener.
     */
    ServiceSearcherBase(int[] attrSet, UUID[] uuidSet, RemoteDevice btDev) {
        super();
        initialize(attrSet, uuidSet, btDev);
    }

    /*
     * Initializes an instance of ServiceSearcherBase
     *
     * @param attrSet list of attributes whose values are requested.
     * @param uuidSet list of UUIDs that indicate services relevant to request.
     * @param btDev remote Bluetooth device to listen response from.
     * @param discListener discovery listener.
     */
    protected void initialize(int[] attrSet, UUID[] uuidSet, RemoteDevice btDev)
        throws IllegalArgumentException, NullPointerException {

        this.btDev = btDev;
        this.attrSet = ServiceSearcherBase.extendByStandardAttrs(attrSet);
        this.uuidSet = ServiceSearcherBase.removeDuplicatedUuids(uuidSet);
    }

    /*
     * Informs this listener about errors during Service Discovering process.
     *
     * @param errorCode error code recieved from server or generated locally
     * due to transaction terminating.
     *
     * @param info detail information about the error
     *
     * @param transactionID ID of transaction response recieved within.
     */
    abstract public void errorResponse(int errorCode, String info,
        int transactionID);

    /*
     * Informs this listener about found services records.
     *
     * @param handleList service records handles returned by server within
     * SDP_ServiceSearchResponse.
     *
     * @param transactionID ID of transaction response recieved within.
     */
    abstract public void serviceSearchResponse(int[] handleList,
        int transactionID);

    /*
     * Informs this listener about found attributes of specified service record.
     *
     * @param attrIDs list of attributes whose values requested from server
     * within SDP_ServiceAttributesRequest.
     *
     * @param attributeValues values returned by server within
     * SDP_ServiceAttributesResponse.
     *
     * @param transactionID ID of transaction response recieved within.
     */
    abstract public void serviceAttributeResponse(int[] attrIDs,
            DataElement[] attributeValues, int transactionID);

    /*
     * Informs this listener about attributes of fisrt found service record.
     */
    abstract public void serviceSearchAttributeResponse(int[] attrIDs,
            DataElement[] attributeValues, int transactionID);
    
    public static int[] extendByStandardAttrs( int[] attrSet ) 
        throws IllegalArgumentException {
        /* Extend attributes IDs with standard values from the spec except duplicates */
        Hashtable uniquies = new Hashtable();
        /* appending by user required attributes */
        if (attrSet != null) {
            if (attrSet.length <= 0 || attrSet.length > ServiceRecordImpl.RETRIEVABLE_MAX) {
                throw new IllegalArgumentException("Invalid attribute set length");
            }
            for (int i=0; i<attrSet.length; i++) {
               if (uniquies.put(new Integer(attrSet[i]), FAKE_VALUE) != null ) {
                   throw new IllegalArgumentException( "Duplicate attribute Ox" + Integer.toHexString(attrSet[i])  );
               }
               if ((attrSet[i] & MASK_OVERFLOW) != 0) {
                   throw new IllegalArgumentException("Illegal attribute ID");
               }
            }        
        }
        /* Adding standard attributes */
        for (int i=0; i<5; i++) {
            uniquies.put(new Integer(i), FAKE_VALUE);
        }
        int [] extendedAttributes = new int[uniquies.size()];        
        Enumeration attrs = uniquies.keys();
        for( int i=0; attrs.hasMoreElements(); i++ ) {
            extendedAttributes[i] = ((Integer) attrs.nextElement()).intValue();
        }
        attrSort(extendedAttributes);
        return extendedAttributes;
    }

    /*
     * Sorts an integer array in ascending order.
     */
    private static void attrSort(int[] data) {

        for (int k = 0; k < data.length - 1; k++)
        {
            boolean isSorted = true;

            for (int i = 1; i < data.length - k; i++)
            {
                if (data[i] < data[i - 1])
                {
                    int tmp = data[i];
                    data[i] = data[i - 1];
                    data[i - 1] = tmp;

                    isSorted = false;

                }
            }

            if (isSorted) 
                break;
        }
    }
    
    public static UUID[] removeDuplicatedUuids( UUID[] uuidSet ) 
        throws IllegalArgumentException, NullPointerException {
        Hashtable uniquies = new Hashtable();
        UUID[] uuids;
        if ((uuidSet != null) && (uuidSet.length > 0)) {
        /* uuid checking */
            for (int i = 0; i < uuidSet.length; i++) {

                if (uuidSet[i] == null) {
                    throw new NullPointerException("Invalid UUID. Null");
                }

                /* check UUID duplication */
                if (uniquies.put(uuidSet[i], FAKE_VALUE) != null) {
                    throw new IllegalArgumentException("Duplicated UUID: " + 
                            uuidSet[i]);
                }
            }

            uuids = new UUID[uniquies.size()];
            Enumeration keys = uniquies.keys();

            for (int i = 0; keys.hasMoreElements(); i++) {
                uuids[i] = (UUID) keys.nextElement();
            }
        } else {
            uuids = new UUID[0];
        }
        return uuids;
    }
}