package net.onrc.onos.core.newintent;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import net.onrc.onos.api.flowmanager.Flow;
import net.onrc.onos.api.flowmanager.FlowBatchHandle;
import net.onrc.onos.api.flowmanager.FlowBatchStateChangedEvent;
import net.onrc.onos.api.flowmanager.FlowId;
import net.onrc.onos.api.flowmanager.FlowManagerListener;
import net.onrc.onos.api.flowmanager.FlowManagerService;
import net.onrc.onos.api.flowmanager.FlowState;
import net.onrc.onos.api.flowmanager.FlowStateChange;
import net.onrc.onos.api.flowmanager.FlowStatesChangedEvent;
import net.onrc.onos.api.newintent.InstallableIntent;
import net.onrc.onos.api.newintent.Intent;
import net.onrc.onos.api.newintent.IntentInstaller;

import java.util.concurrent.CountDownLatch;

import static com.google.common.base.Preconditions.checkNotNull;
import static net.onrc.onos.api.flowmanager.FlowState.FAILED;
import static net.onrc.onos.api.flowmanager.FlowState.INSTALLED;
import static net.onrc.onos.api.flowmanager.FlowState.WITHDRAWN;

// TODO: consider naming because to call Flow manager's API will be removed
// in long-term refactoring
// TODO: consider unifying the install() and remove() by pulling up to this class
/**
 * Base class for implementing an intent installer, which use Flow Manager's API.
 *
 * @param <T> the type of intent
 */
public abstract class AbstractIntentInstaller<T extends InstallableIntent>
        implements IntentInstaller<T> {
    protected final FlowManagerService flowManager;

    /**
     * Constructs a base class with the specified Flow Manager service.
     *
     * @param flowManager Flow manager service, which is used to install/remove
     *                    an intent
     */
    protected AbstractIntentInstaller(FlowManagerService flowManager) {
        this.flowManager = flowManager;
    }

    protected void installFlow(Intent intent, Flow flow) {
        InstallationListener listener = new InstallationListener(flow.getId());
        flowManager.addListener(listener);

        FlowBatchHandle handle = flowManager.addFlow(flow);
        if (handle == null) {
            throw new IntentInstallationException("intent installation failed: " + intent);
        }

        try {
            listener.await();
            if (listener.getFinalState() == FAILED) {
                throw new IntentInstallationException("intent installation failed: " + intent);
            }
        } catch (InterruptedException e) {
            throw new IntentInstallationException("intent installation failed: " + intent, e);
        } finally {
            flowManager.removeListener(listener);
        }
    }

    protected void removeFlow(Intent intent, Flow flow) {
        RemovalListener listener = new RemovalListener(flow.getId());
        flowManager.addListener(listener);

        FlowBatchHandle handle = flowManager.removeFlow(flow.getId());
        if (handle == null) {
            throw new IntentRemovalException("intent removal failed: " + intent);
        }


        try {
            listener.await();
            if (listener.getFinalState() == FAILED) {
                throw new IntentInstallationException("intent removal failed: " + intent);
            }
        } catch (InterruptedException e) {
            throw new IntentInstallationException("intent removal failed: " + intent, e);
        } finally {
            flowManager.removeListener(listener);
        }
    }

    protected abstract static class SyncListener implements FlowManagerListener {
        protected final FlowId target;
        protected final CountDownLatch latch = new CountDownLatch(1);
        protected FlowState finalState;

        protected SyncListener(FlowId target) {
            this.target = checkNotNull(target);
        }

        protected Optional<FlowStateChange> findTargetFlow(FlowStatesChangedEvent event) {
            return Iterables.tryFind(event.getStateChanges(), new Predicate<FlowStateChange>() {
                @Override
                public boolean apply(FlowStateChange stateChange) {
                    return stateChange.getFlowId().equals(target);
                }
            });
        }

        public FlowState getFinalState() {
            return finalState;
        }

        public void await() throws InterruptedException {
            latch.await();
        }
    }

    protected static class InstallationListener extends SyncListener {
        public InstallationListener(FlowId target) {
            super(target);
        }

        @Override
        public void flowStatesChanged(FlowStatesChangedEvent event) {
            Optional<FlowStateChange> optional = findTargetFlow(event);

            if (!optional.isPresent()) {
                return;
            }

            FlowStateChange stateChange = optional.get();
            switch (stateChange.getCurrentState()) {
                case INSTALLED:
                    latch.countDown();
                    finalState = INSTALLED;
                    break;
                case FAILED:
                    latch.countDown();
                    finalState = FAILED;
                    break;
                default:
                    break;
            }
        }

        @Override
        public void flowBatchStateChanged(FlowBatchStateChangedEvent event) {
            // nop
        }
    }

    protected static class RemovalListener extends SyncListener {
        public RemovalListener(FlowId target) {
            super(target);
        }

        @Override
        public void flowStatesChanged(FlowStatesChangedEvent event) {
            Optional<FlowStateChange> optional = findTargetFlow(event);

            if (!optional.isPresent()) {
                return;
            }

            FlowStateChange stateChange = optional.get();
            switch (stateChange.getCurrentState()) {
                case WITHDRAWN:
                    latch.countDown();
                    finalState = WITHDRAWN;
                    break;
                case FAILED:
                    latch.countDown();
                    finalState = FAILED;
                    break;
                default:
                    break;
            }
        }

        @Override
        public void flowBatchStateChanged(FlowBatchStateChangedEvent event) {
            // nop
        }
    }
}

@@ -1203,90 +1203,8 @@
 ing%0A
-// TODO: consider unifying the install() and remove() by pulling up to this class%0A
 /**%0A

Delete TODO comment that is already resolved