/*
 *
 *  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.flex.abc.semantics;

import org.apache.flex.abc.ABCEmitter;
import org.apache.flex.abc.ABCParser;

/**
 * The {@code Metadata} entry provides a mean of embedding arbitrary key/value
 * pairs into the ABC file. The AVM2 will ignore all such entries.
 * <p>
 * Note that, "empty key" and "empty value" are implemented as {@code null} in
 * the key/value arrays by {@link ABCParser} and {@link ABCEmitter}.
 */
public class Metadata
{
    /**
     * A prime number unlikely to be a divisor of the hash table size, used to
     * generate composite hash keys.
     * 
     * @warn if you copy this, pick a new prime to generate distinct hash keys
     * in different classes.
     */
    private static final long PRIME_MULTIPLIER = 5857;

    /**
     * Test two arrays of Comparable objects for equality.
     * 
     * @param array1 array 1
     * @param array2 array 2
     * @return true if the arrays are equal.
     */
    public static <T extends Object> boolean arrayEquals(final T[] array1, final T[] array2)
    {
        boolean result = array1.length == array2.length;
        for (int i = 0; result && i < array1.length; i++)
        {
            if (array1[i] == null && array2[i] == null)
                continue;
            else if (array1[i] != null && array2[i] != null)
                result = array1[i].equals(array2[i]);
            else
                result = false;
        }

        return result;
    }

    /**
     * Constructor.
     */
    public Metadata(final String name, final String[] keys, final String[] values)
    {
        if (name == null)
            throw new IllegalArgumentException("Null name not allowed.");
        if (keys == null)
            throw new IllegalArgumentException("Null key array not allowed.");
        if (values == null)
            throw new IllegalArgumentException("Null value array not allowed.");
        if (keys.length != values.length)
            throw new IllegalArgumentException("Number of keys and values must be same.");

        this.name = name;
        this.keys = keys;
        this.values = values;
    }

    private final String name;
    private final String[] keys;
    private final String[] values;

    /**
     * Cache the hash code since it's fairly expensive to compute.
     */
    private Integer cachedHashCode = null;
    
    public String getName()
    {
        return name;
    }

    public String[] getKeys()
    {
        return keys;
    }

    public String[] getValues()
    {
        return values;
    }

    /**
     * Generate a composite hash code from this item's name and key/value set.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        if (cachedHashCode == null)
        {
            int result = name.hashCode();

            for (String key : keys)
                result = (int)(PRIME_MULTIPLIER * result + (key != null ? key.hashCode() : 0));

            for (String value : values)
                result = (int)(PRIME_MULTIPLIER * result + (value != null ? value.hashCode() : 0));

            cachedHashCode = result;
        }

        return cachedHashCode;
    }

    /**
     * Determine equality by checking the Metadata objects' corresponding
     * fields.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o)
    {
        if (this == o)
        {
            return true;
        }
        else if (!(o instanceof Metadata))
        {
            return false;
        }
        else
        {
            Metadata other = (Metadata)o;

            return this.name.equals(other.name) &&
                   arrayEquals(this.keys, other.keys) &&
                   arrayEquals(this.values, other.values);
        }
    }
}
