/*
 *  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.
 */

/**
* @author Stepan M. Mishura
* @version $Revision$
*/

package org.apache.harmony.security.asn1;

import java.util.Arrays;

import org.apache.harmony.security.internal.nls.Messages;

/**
 * Instance of this class represents ObjectIdentifier (OID).
 * 
 * According to X.690:
 * OID is represented as a sequence of subidentifier.
 * Each subidentifier is represented as non negative integer value.
 * There are at least 2 subidentifiers in the sequence.
 * 
 * Valid values for first subidentifier are 0, 1 and 2.
 * If the first subidentifier has 0 or 1 value the second
 * subidentifier must be less then 40.
 * 
 * @see <a href="http://asn1.elibel.tm.fr/en/standards/index.htm">ASN.1</a>
 */

public final class ObjectIdentifier {

    // OID as array of integers
    private final int[] oid;

    // OID as string
    private String soid;

    /**
     * Creates ObjectIdentifier(OID) from array of integers.
     * 
     * @param oid - array of integers
     * @throws IllegalArgumentException - if oid is invalid or null
     */
    public ObjectIdentifier(int[] oid) {
        validate(oid);
        this.oid = oid;
    }

    /**
     * Creates ObjectIdentifier(OID) from string representation.
     * 
     * @param strOid - oid string
     * @throws IllegalArgumentException - if oid string is invalid or null
     */
    public ObjectIdentifier(String strOid) {
        this.oid = toIntArray(strOid);
        this.soid = strOid;
    }

    /**
     * Returns array of integers.
     * 
     * @return array of integers
     */
    public int[] getOid() {
        return oid;
    }

    /**
     * Compares object with OID for equality.
     * 
     * @return true if object is ObjectIdentifier and it has the same
     *         representation as array of integers, otherwise false
     */
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || this.getClass() != o.getClass()) {
            return false;
        }
        return Arrays.equals(oid, ((ObjectIdentifier) o).oid);
    }

    /**
     * @see java.lang.Object#toString()
     */
    public String toString() {
        if (soid == null) {
            soid = toString(oid);
        }
        return soid;
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    public int hashCode() {
        // FIXME change me to Arrays.hashCode(int[])
        int intHash = 0;
        for (int i = 0; i < oid.length && i < 4; i++) {
            intHash += oid[i] << (8 * i); //TODO what about to find better one?
        }
        return intHash & 0x7FFFFFFF; // only positive
    }

    /**
     * Validates ObjectIdentifier (OID).
     * 
     * @param oid - oid as array of integers
     * @throws IllegalArgumentException - if oid is invalid or null
     */
    public static void validate(int[] oid) {

        if (oid == null) {
            throw new IllegalArgumentException(Messages.getString("security.98")); //$NON-NLS-1$
        }

        if (oid.length < 2) {
            throw new IllegalArgumentException(
                    Messages.getString("security.99")); //$NON-NLS-1$
        }

        if (oid[0] > 2) {
            throw new IllegalArgumentException(
                    Messages.getString("security.9A")); //$NON-NLS-1$
        } else if (oid[0] != 2 && oid[1] > 39) {
            throw new IllegalArgumentException(
                    Messages.getString("security.9B")); //$NON-NLS-1$
        }

        for (int i = 0; i < oid.length; i++) {
            if (oid[i] < 0) {
                throw new IllegalArgumentException(
                        Messages.getString("security.9C")); //$NON-NLS-1$
            }
        }
    }

    // FIXME: implement me
    //    /**
    //     * Validates ObjectIdentifier (OID).
    //     * 
    //     * @param oid - oid as string
    //     * @throws IllegalArgumentException - if oid string  is invalid or null
    //     */
    //    public static void validate(String oid) {
    //
    //        if (oid == null) {
    //            throw new NullPointerException();
    //        }
    //
    //        int length = oid.length();
    //        if (length < 3 || oid.charAt(1) != '.') {
    //            throw new IllegalArgumentException("Invalid oid string");
    //        }
    //
    //        int pos = 2;
    //        int subidentifier = 0;
    //        switch (oid.charAt(0)) {
    //        case '0':
    //        case '1':
    //            for (char c = oid.charAt(pos);;) {
    //                if (c < '0' || c > '9') {
    //                    throw new IllegalArgumentException("Invalid oid string");
    //                } else {
    //                    subidentifier = subidentifier * 10 + c - '0';
    //                }
    //
    //                pos++;
    //                if (pos == length) {
    //                    break;
    //                }
    //
    //                c = oid.charAt(pos);
    //                if (c == '.') {
    //                    pos++;
    //                    if (pos == length) {
    //                        throw new IllegalArgumentException("Invalid oid string");
    //                    }
    //                    break;
    //                }
    //            }
    //
    //            if (subidentifier > 39) {
    //                throw new IllegalArgumentException(
    //                        "If the first subidentifier has 0 or 1 value the second "
    //                                + "subidentifier value MUST be less then 40.");
    //            }
    //            break;
    //        case '2':
    //            break;
    //        default:
    //            throw new IllegalArgumentException(
    //                    "Valid values for first subidentifier are 0, 1 and 2");
    //        }
    //
    //        if (pos == length) {
    //            return;
    //        }
    //
    //        for (char c = oid.charAt(pos);;) {
    //            if (c < '0' || c > '9') {
    //                throw new IllegalArgumentException("Invalid oid string");
    //            }
    //
    //            pos++;
    //            if (pos == length) {
    //                return;
    //            }
    //
    //            c = oid.charAt(pos);
    //            if (c == '.') {
    //                pos++;
    //                if (pos == length) {
    //                    throw new IllegalArgumentException("Invalid oid string");
    //                }
    //            }
    //        }
    //    }

    /**
     * Returns string representation of OID.
     * 
     * Note: it is supposed that passed array of integers
     * contains valid OID value, so no checks are performed. 
     *
     * @param oid - oid as array of integers
     * @return oid string representation
     */
    public static String toString(int[] oid) {
        StringBuffer sb = new StringBuffer(3 * oid.length);

        for (int i = 0; i < oid.length - 1; ++i) {
            sb.append(oid[i]);
            sb.append('.');
        }
        sb.append(oid[oid.length - 1]);
        return sb.toString();
    }

    // BEGIN android-changed
    /**
     * Gets ObjectIdentifier (OID) from string representation.
     * 
     * String representation is defined by the following syntax:
     *     OID = subidentifier 1*("." subidentifier)
     *     subidentifier = 1*(digit)
     * 
     * @param oidString -  string representation of OID
     * @return - oid as array of integers
     * @throws IllegalArgumentException - if oid string is invalid or null
     */
    public static int[] toIntArray(String str) {
        return toIntArray(str, true);
    }
    
    /**
     * Returns whether the given string is a valid ObjectIdentifier
     * (OID) representation.
     * 
     * String representation is defined as for {@link #toIntArray}.
     * 
     * @param oidString -  string representation of OID
     * @return true if oidString has valid syntax or false if not
     */
    public static boolean isOID(String str) {
        return toIntArray(str, false) != null;
    }

    /**
     * Gets ObjectIdentifier (OID) from string representation.
     * 
     * String representation is defined by the following syntax:
     *     OID = subidentifier 1*("." subidentifier)
     *     subidentifier = 1*(digit)
     * 
     * @param oidString -  string representation of OID
     * @return - oid as array of integers or null if the oid string is
     * invalid or null and shouldThrow is false
     * @throws IllegalArgumentException - if oid string is invalid or null and
     * shouldThrow is true
     */
    private static int[] toIntArray(String str, boolean shouldThrow) {
        if (str == null) {
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(
                    Messages.getString("security.9D")); //$NON-NLS-1$
        }

        int length = str.length();
        if (length == 0) {
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(Messages.getString("security.9E")); //$NON-NLS-1$
        }

        int count = 1; // number of subidentifiers
        boolean wasDot = true; // indicates whether char before was dot or not.
        char c; // current char
        for (int i = 0; i < length; i++) {
            c = str.charAt(i);
            if (c == '.') {
                if (wasDot) {
                    if (! shouldThrow) {
                        return null;
                    }
                    throw new IllegalArgumentException(Messages.getString("security.9E")); //$NON-NLS-1$
                }
                wasDot = true;
                count++;
            } else if (c >= '0' && c <= '9') {
                wasDot = false;
            } else {
                if (! shouldThrow) {
                    return null;
                }
                throw new IllegalArgumentException(Messages.getString("security.9E")); //$NON-NLS-1$
            }
        }

        if (wasDot) {
            // the last char is dot
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(Messages.getString("security.9E")); //$NON-NLS-1$
        }

        if (count < 2) {
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(
                    Messages.getString("security.99")); //$NON-NLS-1$
        }

        int[] oid = new int[count];
        for (int i = 0, j = 0; i < length; i++) {
            c = str.charAt(i);
            if (c == '.') {
                j++;
            } else {
                oid[j] = oid[j] * 10 + c - 48; // '0' = 48
            }
        }

        if (oid[0] > 2) {
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(
                    Messages.getString("security.9A")); //$NON-NLS-1$
        } else if (oid[0] != 2 && oid[1] > 39) {
            if (! shouldThrow) {
                return null;
            }
            throw new IllegalArgumentException(
                    Messages.getString("security.9B")); //$NON-NLS-1$
        }

        return oid;
    }
    // END android-changed
}
