/*
 * 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.brooklyn.core.mgmt.rebind;

import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState;
import org.apache.brooklyn.api.mgmt.rebind.RebindExceptionHandler;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoPersister;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData;
import org.apache.brooklyn.api.mgmt.rebind.mementos.BrooklynMementoRawData.Builder;
import org.apache.brooklyn.api.mgmt.rebind.mementos.Memento;
import org.apache.brooklyn.api.objs.BrooklynObject;
import org.apache.brooklyn.api.objs.BrooklynObjectType;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.mgmt.persist.BrooklynMementoPersisterToObjectStore;
import org.apache.brooklyn.core.mgmt.persist.PersistenceActivityMetrics;
import org.apache.brooklyn.core.mgmt.rebind.transformer.CompoundTransformer;
import org.apache.brooklyn.core.objs.BrooklynObjectInternal;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;

/**
 * Replaces a set of existing entities (and their adjuncts) and locations:
 * writes their state, applies a transformation, then reads the state back.
 */
public class ActivePartialRebindIteration extends RebindIteration {

    private static final Logger LOG = LoggerFactory.getLogger(ActivePartialRebindIteration.class);
    
    protected Iterator<BrooklynObject> objectsToRebindInitial;
    protected Collection<BrooklynObject> objectsToRebindFinal;
    protected List<CompoundTransformer> transformers = MutableList.of();
    
    public ActivePartialRebindIteration(RebindManagerImpl rebindManager, 
            ManagementNodeState mode,
            ClassLoader classLoader, RebindExceptionHandler exceptionHandler,
            Semaphore rebindActive, AtomicInteger readOnlyRebindCount, PersistenceActivityMetrics rebindMetrics, BrooklynMementoPersister persistenceStoreAccess
            ) {
        super(rebindManager, mode, classLoader, exceptionHandler, rebindActive, readOnlyRebindCount, rebindMetrics, persistenceStoreAccess);
    }

    @Override
    protected boolean isRebindingActiveAgain() {
        return true;
    }
    
    public void setObjectIterator(Iterator<BrooklynObject> objectsToRebind) {
        this.objectsToRebindInitial = objectsToRebind;
    }
    
    public void applyTransformer(CompoundTransformer transformer) {
        transformers.add(Preconditions.checkNotNull(transformer, "transformer"));
    }
    
    @Override
    protected void doRun() throws Exception {
        Preconditions.checkState(rebindManager.getRebindMode()==ManagementNodeState.MASTER, "Partial rebind only supported in master mode, not "+rebindManager.getRebindMode());
        Preconditions.checkState(readOnlyRebindCount.get()==Integer.MIN_VALUE, "Rebind count should be MIN when running in master mode");
        Preconditions.checkNotNull(objectsToRebindInitial, "Objects to rebind must be set");

        LOG.debug("Partial rebind - rebinding ("+mode+") from "+rebindManager.getPersister().getBackingStoreDescription()+"...");

        super.doRun();
    }
    
    /** Rather than loading from the remote persistence store (as {@link InitialFullRebindIteration} does),
     * this constructs the memento data by serializing the objects we are replacing. 
     * TODO: Currently this does not do any pausing or unmanagement or guarding write access,
     * so there is a short window for data loss between this write and the subsequent read.
     */
    @Override
    protected void loadManifestFiles() throws Exception {
        checkEnteringPhase(1);
        Builder mementoRawBuilder = BrooklynMementoRawData.builder();

        /*
         * Unmanagement is done as part of the "manage" call, entity by entity.
         */

        objectsToRebindFinal = MutableSet.of();
        while (objectsToRebindInitial.hasNext()) {
            BrooklynObject bo = objectsToRebindInitial.next();
            objectsToRebindFinal.add(bo);
            
            if (bo instanceof Entity) {
                // if it's an entity, add all adjuncts. (if doing some sort of pause, that's maybe not necessary...)
                objectsToRebindFinal.addAll( ((EntityInternal)bo).policies().asList() );
                objectsToRebindFinal.addAll( ((EntityInternal)bo).enrichers().asList() );
                objectsToRebindFinal.addAll( ((EntityInternal)bo).feeds().getFeeds() );
            }
        }
        
        // get serialization
        for (BrooklynObject bo: objectsToRebindFinal) {
            Memento m = ((BrooklynObjectInternal)bo).getRebindSupport().getMemento();
            BrooklynMementoPersister p = rebindManager.getPersister();
            String mr = ((BrooklynMementoPersisterToObjectStore)p).getMementoSerializer().toString(m);
            mementoRawBuilder.put(BrooklynObjectType.of(bo), bo.getId(), mr);
        }
        // then rebuild
        mementoRawData = mementoRawBuilder.build();

        preprocessManifestFiles();
    }
    
    @Override
    protected void initPlaneId() {
        // managementPlaneId is already initialized, no need to set it on partial rebind
    }
    
    @Override
    protected void preprocessManifestFiles() throws Exception {
        for (CompoundTransformer transformer: transformers) {
            mementoRawData = transformer.transform(mementoRawData);
        }
        super.preprocessManifestFiles();
        overwritingMaster = true;
    }

    @Override
    protected void installBundlesAndRebuildCatalog() {
        checkEnteringPhase(2);
        
        // skip; bundles and catalog items can't be changed for a partial rebind instruction
        // (if upgrading, they should be changed beforehand, then this used to upgrade the objects)
    }
    
    @Override
    protected Collection<String> getMementoRootEntities() {
        // all entities are roots here, because we are not recursing
        return memento.getEntityIds();
    }
    
    @Override
    protected void cleanupOldLocations(Set<String> oldLocations) {
        // not applicable here
    }
    @Override
    protected void cleanupOldEntities(Set<String> oldEntities) {
        // not applicable here
    }

}
