/*
 * Copyright (c) 2009, Rickard Öberg. 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.spi.entitystore;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import org.qi4j.api.entity.EntityReference;
import org.qi4j.api.injection.scope.This;
import org.qi4j.api.mixin.Mixins;
import org.qi4j.api.usecase.Usecase;
import org.qi4j.spi.entity.EntityState;
import org.qi4j.spi.entity.EntityStatus;
import org.qi4j.spi.module.ModuleSpi;

/**
 * Entity versions state.
 */
@Mixins( EntityStateVersions.EntityStateVersionsMixin.class )
public interface EntityStateVersions
{
    void forgetVersions( Iterable<EntityState> states );

    void rememberVersion( EntityReference identity, String version );

    void checkForConcurrentModification( Iterable<EntityState> loaded, ModuleSpi module, long currentTime )
        throws ConcurrentEntityStateModificationException;

    /**
     * Entity versions state mixin.
     */
    class EntityStateVersionsMixin
        implements EntityStateVersions
    {
        @This
        private EntityStore store;

        private final Map<EntityReference, String> versions = new WeakHashMap<>();

        @Override
        public synchronized void forgetVersions( Iterable<EntityState> states )
        {
            for( EntityState state : states )
            {
                versions.remove( state.identity() );
            }
        }

        @Override
        public synchronized void rememberVersion( EntityReference identity, String version )
        {
            versions.put( identity, version );
        }

        @Override
        public synchronized void checkForConcurrentModification( Iterable<EntityState> loaded,
                                                                 ModuleSpi module,
                                                                 long currentTime
        )
            throws ConcurrentEntityStateModificationException
        {
            List<EntityReference> changed = null;
            for( EntityState entityState : loaded )
            {
                if( entityState.status().equals( EntityStatus.NEW ) )
                {
                    continue;
                }

                String storeVersion = versions.get( entityState.identity() );
                if( storeVersion == null )
                {
                    EntityStoreUnitOfWork unitOfWork = store.newUnitOfWork( Usecase.DEFAULT, module, currentTime );
                    EntityState state = unitOfWork.entityStateOf( module, entityState.identity() );
                    storeVersion = state.version();
                    unitOfWork.discard();
                }

                if( !entityState.version().equals( storeVersion ) )
                {
                    if( changed == null )
                    {
                        changed = new ArrayList<>();
                    }
                    changed.add( entityState.identity() );
                }
            }

            if( changed != null )
            {
                throw new ConcurrentEntityStateModificationException( changed );
            }
        }
    }
}
