/*
 * Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
 * Copyright (c) 2007, Niclas Hedhman. All Rights Reserved.
 * Copyright (c) 2007, Alin Dreghiciu. All Rights Reserved.
 * Copyright (c) 2012, Paul Merlin. All Rights Reserved.
 *
 * Licensed 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.qi4j.runtime.value;

import java.lang.reflect.Proxy;
import org.qi4j.api.composite.CompositeInstance;
import org.qi4j.api.value.ValueComposite;
import org.qi4j.runtime.association.AssociationModel;
import org.qi4j.runtime.association.ManyAssociationModel;
import org.qi4j.runtime.association.NamedAssociationModel;
import org.qi4j.runtime.composite.MixinsInstance;
import org.qi4j.runtime.composite.TransientInstance;
import org.qi4j.runtime.property.PropertyInstance;
import org.qi4j.runtime.property.PropertyModel;
import org.qi4j.spi.module.ModuleSpi;

/**
 * ValueComposite instance
 */
public final class ValueInstance
    extends TransientInstance
    implements CompositeInstance, MixinsInstance
{
    public static ValueInstance valueInstanceOf( ValueComposite composite )
    {
        return (ValueInstance) Proxy.getInvocationHandler( composite );
    }

    public ValueInstance( ValueModel compositeModel,
                          ModuleSpi moduleInstance,
                          Object[] mixins,
                          ValueStateInstance state
    )
    {
        super( compositeModel, moduleInstance, mixins, state );
    }

    /**
     * Perform equals with {@code o} argument.
     * <p>
     * The definition of equals() for the Value is that if both the state and descriptor are equal,
     * then the values are equal.
     * </p>
     *
     * @param o The other object to compare.
     *
     * @return Returns a {@code boolean} indicator whether this object is equals the other.
     */
    @Override
    public boolean equals( Object o )
    {
        if( this == o )
        {
            return true;
        }
        if( o == null || !Proxy.isProxyClass( o.getClass() ) )
        {
            return false;
        }

        try
        {
            ValueInstance that = (ValueInstance) Proxy.getInvocationHandler( o );
            // Descriptor equality
            if( !descriptor().equals( that.descriptor() ) )
            {
                return false;
            }
            // State equality
            return state.equals( that.state );
        }
        catch( ClassCastException e )
        {
            return false;
        }
    }

    @Override
    public ValueStateInstance state()
    {
        return (ValueStateInstance) state;
    }

    @Override
    public ValueModel descriptor()
    {
        return (ValueModel) compositeModel;
    }

    /**
     * When a ValueBuilder is about to start, ensure that all state has builder infos, i.e. they are mutable.
     */
    public void prepareToBuild()
    {
        for( PropertyModel propertyDescriptor : descriptor().state().properties() )
        {
            PropertyInstance<Object> propertyInstance =
                (PropertyInstance<Object>) state.propertyFor( propertyDescriptor.accessor() );

            propertyInstance.prepareToBuild( propertyDescriptor );
        }

        for( AssociationModel associationDescriptor : descriptor().state().associations() )
        {
            state().associationFor( associationDescriptor.accessor() )
                .setAssociationInfo( associationDescriptor.getBuilderInfo() );
        }

        for( ManyAssociationModel associationDescriptor : descriptor().state().manyAssociations() )
        {
            state().manyAssociationFor( associationDescriptor.accessor() )
                .setAssociationInfo( associationDescriptor.getBuilderInfo() );
        }

        for( NamedAssociationModel associationDescriptor : descriptor().state().namedAssociations() )
        {
            state().namedAssociationFor( associationDescriptor.accessor() )
                .setAssociationInfo( associationDescriptor.getBuilderInfo() );
        }
    }

    /**
     * When a ValueBuilder is finished and is about to instantiate a Value, call this to ensure that the state has correct
     * settings, i.e. is immutable.
     */
    public void prepareBuilderState()
    {
        for( PropertyModel propertyDescriptor : descriptor().state().properties() )
        {
            PropertyInstance<Object> propertyInstance =
                (PropertyInstance<Object>) state.propertyFor( propertyDescriptor.accessor() );
            propertyInstance.prepareBuilderState( propertyDescriptor );
        }

        for( AssociationModel associationDescriptor : descriptor().state().associations() )
        {
            state().associationFor( associationDescriptor.accessor() ).setAssociationInfo( associationDescriptor );
        }

        for( ManyAssociationModel associationDescriptor : descriptor().state().manyAssociations() )
        {
            state().manyAssociationFor( associationDescriptor.accessor() ).setAssociationInfo( associationDescriptor );
        }

        for( NamedAssociationModel associationDescriptor : descriptor().state().namedAssociations() )
        {
            state().namedAssociationFor( associationDescriptor.accessor() ).setAssociationInfo( associationDescriptor );
        }
    }

    /**
     * Calculate hash code.
     *
     * @return the hashcode of this instance.
     */
    @Override
    public int hashCode()
    {
        int hash = compositeModel.hashCode() * 23; // Descriptor
        return hash + state.hashCode() * 5; // State
    }

    @Override
    public String toString()
    {
        return ( (ModuleSpi) module() ).valueSerialization().serialize( this.<ValueComposite>proxy() );
    }
}
