package org.infinispan.cli.interpreter.statement;

import static org.infinispan.cli.interpreter.logging.Messages.MSG;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.infinispan.Cache;
import org.infinispan.cli.interpreter.logging.Log;
import org.infinispan.cli.interpreter.result.Result;
import org.infinispan.cli.interpreter.result.StatementException;
import org.infinispan.cli.interpreter.result.StringResult;
import org.infinispan.cli.interpreter.session.Session;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.upgrade.RollingUpgradeManager;
import org.infinispan.util.logging.LogFactory;

/**
 * Performs operation related to rolling upgrades
 *
 * @author Tristan Tarrant
 * @since 5.2
 */
public class UpgradeStatement implements Statement {
   private static final Log log = LogFactory.getLog(UpgradeStatement.class, Log.class);

   private enum Options {
      ALL, DUMPKEYS, SYNCHRONIZE, DISCONNECTSOURCE
   }

   final String cacheName;
   final private List<Option> options;

   public UpgradeStatement(List<Option> options, String cacheName) {
      this.options = options;
      this.cacheName = cacheName;
   }

   @Override
   public Result execute(Session session) throws StatementException {
      boolean all = false;
      UpgradeMode mode = UpgradeMode.NONE;
      String migratorName = null;

      for (Option opt : options) {
         switch (opt.toEnum(Options.class)) {
         case ALL: {
            all = true;
            break;
         }
         case SYNCHRONIZE: {
            mode = UpgradeMode.SYNCHRONIZE;
            migratorName = opt.getParameter();
            if (migratorName == null) {
               throw log.missingMigrator();
            }
            break;
         }
         case DISCONNECTSOURCE: {
            mode = UpgradeMode.DISCONNECTSOURCE;
            migratorName = opt.getParameter();
            if (migratorName == null) {
               throw log.missingMigrator();
            }
         }
         }
      }
      StringBuilder sb = new StringBuilder();
      switch (mode) {
      case SYNCHRONIZE: {
         for (Cache<?, ?> cache : all ? getAllCaches(session) : Collections.singletonList(session.getCache(cacheName))) {
            RollingUpgradeManager upgradeManager = cache.getAdvancedCache().getComponentRegistry().getComponent(RollingUpgradeManager.class);
            try {
               long count = upgradeManager.synchronizeData(migratorName);
               sb.append(MSG.synchronizedEntries(count, migratorName, cache.getName()));
               sb.append("\n");
            } catch (Exception e) {
               throw log.dataSynchronizationError(e, cache.getName(), migratorName);
            }
         }
         break;
      }
      case DISCONNECTSOURCE: {
         for (Cache<?, ?> cache : all ? getAllCaches(session) : Collections.singletonList(session.getCache(cacheName))) {
            RollingUpgradeManager upgradeManager = cache.getAdvancedCache().getComponentRegistry().getComponent(RollingUpgradeManager.class);
            try {
               upgradeManager.disconnectSource(migratorName);
               sb.append(MSG.disonnectedSource(migratorName, cache.getName()));
               sb.append("\n");
            } catch (Exception e) {
               throw log.sourceDisconnectionError(e, cache.getName(), migratorName);
            }
         }
         break;
      }
      default: {
         throw log.missingUpgradeAction();
      }
      }
      return new StringResult(sb.toString());
   }

   private List<Cache<?, ?>> getAllCaches(Session session) {
      List<Cache<?, ?>> caches = new ArrayList<>();
      EmbeddedCacheManager container = session.getCacheManager();
      for (String cacheName : container.getCacheNames()) {
         if (container.isRunning(cacheName)) {
            caches.add(session.getCache(cacheName));
         }
      }
      caches.add(container.getCache());

      return caches;
   }

   private enum UpgradeMode {
      NONE, SYNCHRONIZE, DISCONNECTSOURCE
   }
}
