/*
 *  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.polygene.runtime.unitofwork;

import org.apache.polygene.api.entity.EntityBuilder;
import org.apache.polygene.api.entity.EntityDescriptor;
import org.apache.polygene.api.entity.EntityReference;
import org.apache.polygene.api.entity.LifecycleException;
import org.apache.polygene.api.identity.Identity;
import org.apache.polygene.runtime.composite.FunctionStateResolver;
import org.apache.polygene.runtime.entity.EntityInstance;
import org.apache.polygene.runtime.entity.EntityModel;
import org.apache.polygene.spi.entity.EntityState;
import org.apache.polygene.spi.entitystore.EntityStoreUnitOfWork;
import org.apache.polygene.spi.module.ModuleSpi;

import static org.apache.polygene.api.identity.HasIdentity.IDENTITY_STATE_NAME;

/**
 * Implementation of EntityBuilder. Maintains an instance of the entity which
 * will not have its state validated until it is created by calling newInstance().
 */
public final class EntityBuilderInstance<T>
    implements EntityBuilder<T>
{
    private final EntityModel model;
    private final ModuleUnitOfWork uow;
    private final EntityStoreUnitOfWork store;
    private Identity identity;

    private final BuilderEntityState entityState;
    private final EntityInstance prototypeInstance;

    public EntityBuilderInstance(
        EntityDescriptor model,
        ModuleUnitOfWork uow,
        EntityStoreUnitOfWork store,
        Identity identity
    )
    {
        this( model, uow, store, identity, null );
    }

    public EntityBuilderInstance(
        EntityDescriptor model,
        ModuleUnitOfWork uow,
        EntityStoreUnitOfWork store,
        Identity identity,
        FunctionStateResolver stateResolver
    )
    {
        this.model = (EntityModel) model;
        this.uow = uow;
        this.store = store;
        this.identity = identity;
        EntityReference reference = EntityReference.create( identity );
        entityState = new BuilderEntityState( model, reference );
        this.model.initState( model.module(), entityState );
        if( stateResolver != null )
        {
            stateResolver.populateState( this.model, entityState );
        }
        entityState.setPropertyValue( IDENTITY_STATE_NAME, identity );
        prototypeInstance = this.model.newInstance( uow, (ModuleSpi) model.module().instance(), entityState );
    }

    @SuppressWarnings( "unchecked" )
    @Override
    public T instance()
    {
        checkValid();
        return prototypeInstance.proxy();
    }

    @Override
    public <K> K instanceFor( Class<K> mixinType )
    {
        checkValid();
        return prototypeInstance.newProxy( mixinType );
    }

    @Override
    @SuppressWarnings( "unchecked" )
    public T newInstance()
        throws LifecycleException
    {
        checkValid();

        // Figure out whether to use given or generated reference
        Identity identity = (Identity) entityState.propertyValueOf( IDENTITY_STATE_NAME );
        EntityReference entityReference = EntityReference.create( identity );
        EntityState newEntityState = model.newEntityState( store, entityReference );

        prototypeInstance.invokeCreate();

        // Check constraints
        prototypeInstance.checkConstraints();

        entityState.copyTo( newEntityState );

        EntityInstance instance = model.newInstance( uow, (ModuleSpi) model.module().instance(), newEntityState );

        Object proxy = instance.proxy();

        // Add entity in UOW
        uow.addEntity( instance );

        // Invalidate builder
        this.identity = null;

        return (T) proxy;
    }

    private void checkValid()
        throws IllegalStateException
    {
        if( identity == null )
        {
            throw new IllegalStateException( "EntityBuilder is not valid after call to newInstance()" );
        }
    }
}
