/*
   * (non-Javadoc)
   *
   * @see
   * com.emc.storageos.volumecontroller.BlockStorageDevice#doWaitForSynchronized
   * (java.lang.Class, com.emc.storageos.db.client.model.StorageSystem,
   * java.net.URI, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doWaitForSynchronized(
      Class<? extends BlockObject> clazz,
      StorageSystem storageObj,
      URI target,
      TaskCompleter completer) {
    log.info("START waitForSynchronized for {}", target);

    try {
      Volume targetObj = dbClient.queryObject(Volume.class, target);
      // Source could be either Volume or BlockSnapshot
      BlockObject sourceObj = BlockObject.fetch(dbClient, targetObj.getAssociatedSourceVolume());

      // We split the pair which causes the data to be synchronized.
      // When the split is complete that data is synchronized.
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageObj),
              storageObj.getSmisUserName(),
              storageObj.getSmisPassword());
      HDSApiProtectionManager hdsApiProtectionManager = hdsApiClient.getHdsApiProtectionManager();
      String replicationGroupObjectID = hdsApiProtectionManager.getReplicationGroupObjectId();
      ReplicationInfo replicationInfo =
          hdsApiProtectionManager.getReplicationInfoFromSystem(
                  sourceObj.getNativeId(), targetObj.getNativeId())
              .first;
      hdsApiProtectionManager.modifyShadowImagePair(
          replicationGroupObjectID,
          replicationInfo.getObjectID(),
          HDSApiProtectionManager.ShadowImageOperationType.split);

      // Update state in case we are waiting for synchronization
      // after creation of a new full copy that was not created
      // inactive.
      String state = targetObj.getReplicaState();
      if (!ReplicationState.SYNCHRONIZED.name().equals(state)) {
        targetObj.setSyncActive(true);
        targetObj.setReplicaState(ReplicationState.SYNCHRONIZED.name());
        dbClient.persistObject(targetObj);
      }

      // Queue job to wait for replication status to move to split.
      ControllerServiceImpl.enqueueJob(
          new QueueJob(
              new HDSReplicationSyncJob(
                  storageObj.getId(),
                  sourceObj.getNativeId(),
                  targetObj.getNativeId(),
                  ReplicationStatus.SPLIT,
                  completer)));
    } catch (Exception e) {
      log.error("Exception occurred while waiting for synchronization", e);
      ServiceError serviceError = DeviceControllerException.errors.jobFailed(e);
      completer.error(dbClient, serviceError);
    }
    log.info("completed doWaitForSynchronized");
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doExpandVolume(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.StoragePool, com.emc.storageos.db.client.model.Volume, java.lang.Long,
   * com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doExpandVolume(
      StorageSystem storageSystem,
      StoragePool storagePool,
      Volume volume,
      Long size,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {

    log.info(
        String.format(
            "Expand Volume Start - Array: %s, Pool: %s, Volume: %s, New size: %d",
            storageSystem.getSerialNumber(), storagePool.getNativeGuid(), volume.getLabel(), size));
    try {
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectID = HDSUtils.getSystemObjectID(storageSystem);
      String asyncTaskMessageId = null;

      if (volume.getThinlyProvisioned()) {
        asyncTaskMessageId =
            hdsApiClient.modifyThinVolume(
                systemObjectID,
                HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storageSystem),
                size);
      }

      if (null != asyncTaskMessageId) {
        HDSJob expandVolumeJob =
            new HDSVolumeExpandJob(
                asyncTaskMessageId,
                storageSystem.getId(),
                storagePool.getId(),
                taskCompleter,
                "ExpandVolume");
        ControllerServiceImpl.enqueueJob(new QueueJob(expandVolumeJob));
      }
    } catch (final InternalException e) {
      log.error("Problem in doExpandVolume: ", e);
      taskCompleter.error(dbClient, e);
    } catch (final Exception e) {
      log.error("Problem in doExpandVolume: ", e);
      ServiceError serviceError =
          DeviceControllerErrors.hds.methodFailed("doExpandVolume", e.getMessage());
      taskCompleter.error(dbClient, serviceError);
    }
    log.info(
        String.format(
            "Expand Volume End - Array: %s, Pool: %s, Volume: %s",
            storageSystem.getSerialNumber(), storagePool.getNativeGuid(), volume.getLabel()));
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doCleanupMetaMembers(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.Volume,
   * com.emc.storageos.volumecontroller.impl.block.taskcompleter.CleanupMetaVolumeMembersCompleter)
   */
  @Override
  public void doCleanupMetaMembers(
      StorageSystem storageSystem,
      Volume volume,
      CleanupMetaVolumeMembersCompleter cleanupCompleter)
      throws DeviceControllerException {
    // Remove meta member volumes from storage device
    try {
      log.info(
          String.format(
              "doCleanupMetaMembers  Start - Array: %s, Volume: %s",
              storageSystem.getSerialNumber(), volume.getLabel()));
      // Load meta volume members from WF data
      String sourceStepId = cleanupCompleter.getSourceStepId();
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getUsername(),
              storageSystem.getSmisPassword());
      List<String> metaMembers =
          (ArrayList<String>) WorkflowService.getInstance().loadStepData(sourceStepId);
      if (metaMembers != null && !metaMembers.isEmpty()) {
        log.info(
            String.format(
                "doCleanupMetaMembers: Members stored for meta volume: %n %s", metaMembers));
        // Check if volumes still exist in array and if it is not composite member (already
        // added to the meta volume)
        Set<String> volumeIds = new HashSet<String>();
        for (String logicalUnitObjectId : metaMembers) {
          LogicalUnit logicalUnit =
              hdsApiClient.getLogicalUnitInfo(
                  HDSUtils.getSystemObjectID(storageSystem), logicalUnitObjectId);
          if (logicalUnit != null) {
            log.debug(
                "doCleanupMetaMembers: Volume: "
                    + logicalUnitObjectId
                    + ", Usage of volume: "
                    + logicalUnit.getComposite());
            if (logicalUnit.getComposite() != HDSConstants.COMPOSITE_ELEMENT_MEMBER) {
              volumeIds.add(logicalUnitObjectId);
            }
          }
        }
        if (volumeIds.isEmpty()) {
          cleanupCompleter.setSuccess(true);
          log.info("doCleanupMetaMembers: No meta members to cleanup in array.");
        } else {
          log.info(
              String.format(
                  "doCleanupMetaMembers: Members to cleanup in array: %n   %s", volumeIds));
          // Prepare parameters and call method to delete meta members from array

          HDSCleanupMetaVolumeMembersJob hdsJobCompleter = null;
          // When "cleanup" is separate workflow step, call async (for example rollback
          // step in volume expand)
          // Otherwise, call synchronously (for example when cleanup is part of meta
          // volume create rollback)
          String asyncMessageId =
              hdsApiClient.deleteThickLogicalUnits(
                  HDSUtils.getSystemObjectID(storageSystem), volumeIds);

          if (cleanupCompleter.isWFStep()) {
            if (asyncMessageId != null) {
              ControllerServiceImpl.enqueueJob(
                  new QueueJob(
                      new HDSCleanupMetaVolumeMembersJob(
                          asyncMessageId,
                          storageSystem.getId(),
                          volume.getId(),
                          cleanupCompleter)));
            }
          } else {
            // invoke synchronously
            hdsJobCompleter =
                new HDSCleanupMetaVolumeMembersJob(
                    asyncMessageId, storageSystem.getId(), volume.getId(), cleanupCompleter);
            ((HDSMetaVolumeOperations) metaVolumeOperations)
                .invokeMethodSynchronously(hdsApiFactory, asyncMessageId, hdsJobCompleter);
          }
        }
      } else {
        log.info(
            "doCleanupMetaMembers: No meta members stored for meta volume. Nothing to cleanup in array.");
        cleanupCompleter.setSuccess(true);
      }
    } catch (Exception e) {
      log.error("Problem in doCleanupMetaMembers: ", e);
      ServiceError error =
          DeviceControllerErrors.smis.methodFailed("doCleanupMetaMembers", e.getMessage());
      cleanupCompleter.setError(error);
      cleanupCompleter.setSuccess(false);
    }
    log.info(
        String.format(
            "doCleanupMetaMembers End - Array: %s,  Volume: %s",
            storageSystem.getSerialNumber(), volume.getLabel()));
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doDeleteVolumes(com.emc.storageos.db.client.model.StorageSystem,
   * java.lang.String, java.util.List, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doDeleteVolumes(
      StorageSystem storageSystem, String opId, List<Volume> volumes, TaskCompleter taskCompleter)
      throws DeviceControllerException {

    try {
      StringBuilder logMsgBuilder =
          new StringBuilder(
              String.format("Delete Volume Start - Array:%s", storageSystem.getSerialNumber()));
      MultiVolumeTaskCompleter multiVolumeTaskCompleter = (MultiVolumeTaskCompleter) taskCompleter;
      Set<String> thickLogicalUnitIdList = new HashSet<String>();
      Set<String> thinLogicalUnitIdList = new HashSet<String>();
      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectId = HDSUtils.getSystemObjectID(storageSystem);
      log.info("volumes size: {}", volumes.size());
      for (Volume volume : volumes) {
        logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
        String logicalUnitObjectId =
            HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storageSystem);
        LogicalUnit logicalUnit =
            hdsApiClient.getLogicalUnitInfo(systemObjectId, logicalUnitObjectId);
        if (logicalUnit == null) {
          // related volume state (if any) has been deleted. skip
          // processing, if already deleted from array.
          log.info(String.format("Volume %s already deleted: ", volume.getNativeId()));
          volume.setInactive(true);
          dbClient.persistObject(volume);
          VolumeTaskCompleter deleteTaskCompleter =
              multiVolumeTaskCompleter.skipTaskCompleter(volume.getId());
          deleteTaskCompleter.ready(dbClient);
          continue;
        }
        if (volume.getThinlyProvisioned()) {
          thinLogicalUnitIdList.add(logicalUnitObjectId);
        } else {
          thickLogicalUnitIdList.add(logicalUnitObjectId);
        }
      }
      log.info(logMsgBuilder.toString());
      if (!multiVolumeTaskCompleter.isVolumeTaskCompletersEmpty()) {
        if (null != thickLogicalUnitIdList && !thickLogicalUnitIdList.isEmpty()) {
          String asyncThickLUsJobId =
              hdsApiClient.deleteThickLogicalUnits(systemObjectId, thickLogicalUnitIdList);
          if (null != asyncThickLUsJobId) {
            ControllerServiceImpl.enqueueJob(
                new QueueJob(
                    new HDSDeleteVolumeJob(
                        asyncThickLUsJobId, volumes.get(0).getStorageController(), taskCompleter)));
          }
        }

        if (null != thinLogicalUnitIdList && !thinLogicalUnitIdList.isEmpty()) {
          String asyncThinHDSJobId =
              hdsApiClient.deleteThinLogicalUnits(systemObjectId, thinLogicalUnitIdList);

          // Not sure whether this really works as tracking two jobs
          // in single operation.
          if (null != asyncThinHDSJobId) {
            ControllerServiceImpl.enqueueJob(
                new QueueJob(
                    new HDSDeleteVolumeJob(
                        asyncThinHDSJobId, volumes.get(0).getStorageController(), taskCompleter)));
          }
        }
      } else {
        // If we are here, there are no volumes to delete, we have
        // invoked ready() for the VolumeDeleteCompleter, and told
        // the multiVolumeTaskCompleter to skip these completers.
        // In this case, the multiVolumeTaskCompleter complete()
        // method will not be invoked and the result is that the
        // workflow that initiated this delete request will never
        // be updated. So, here we just call complete() on the
        // multiVolumeTaskCompleter to ensure the workflow status is
        // updated.
        multiVolumeTaskCompleter.ready(dbClient);
      }
    } catch (Exception e) {
      log.error("Problem in doDeleteVolume: ", e);
      ServiceError error =
          DeviceControllerErrors.hds.methodFailed("doDeleteVolume", e.getMessage());
      taskCompleter.error(dbClient, error);
    }
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format("Delete Volume End - Array: %s", storageSystem.getSerialNumber()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
    }
    log.info(logMsgBuilder.toString());
  }
  /*
   * (non-Javadoc)
   *
   * @see com.emc.storageos.volumecontroller.BlockStorageDevice#doCreateVolumes(com.emc.storageos.db.client.model.StorageSystem,
   * com.emc.storageos.db.client.model.StoragePool, java.lang.String, java.util.List,
   * com.emc.storageos.volumecontroller.impl.utils.VirtualPoolCapabilityValuesWrapper, com.emc.storageos.volumecontroller.TaskCompleter)
   */
  @Override
  public void doCreateVolumes(
      StorageSystem storageSystem,
      StoragePool storagePool,
      String opId,
      List<Volume> volumes,
      VirtualPoolCapabilityValuesWrapper capabilities,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {
    String label = null;
    Long capacity = null;
    boolean isThinVolume = false;
    boolean opCreationFailed = false;
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format(
                "Create Volume Start - Array:%s, Pool:%s",
                storageSystem.getSerialNumber(), storagePool.getNativeGuid()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(
          String.format(
              "%nVolume:%s , IsThinlyProvisioned: %s",
              volume.getLabel(), volume.getThinlyProvisioned()));

      if ((label == null) && (volumes.size() == 1)) {
        String tenantName = "";
        try {
          TenantOrg tenant = dbClient.queryObject(TenantOrg.class, volume.getTenant().getURI());
          tenantName = tenant.getLabel();
        } catch (DatabaseException e) {
          log.error("Error lookup TenantOrb object", e);
        }
        label =
            nameGenerator.generate(
                tenantName,
                volume.getLabel(),
                volume.getId().toString(),
                '-',
                HDSConstants.MAX_VOLUME_NAME_LENGTH);
      }

      if (capacity == null) {
        capacity = volume.getCapacity();
      }
      isThinVolume = volume.getThinlyProvisioned();
    }
    log.info(logMsgBuilder.toString());
    try {
      multiVolumeCheckForHitachiModel(volumes, storageSystem);

      HDSApiClient hdsApiClient =
          hdsApiFactory.getClient(
              HDSUtils.getHDSServerManagementServerInfo(storageSystem),
              storageSystem.getSmisUserName(),
              storageSystem.getSmisPassword());
      String systemObjectID = HDSUtils.getSystemObjectID(storageSystem);
      String poolObjectID = HDSUtils.getPoolObjectID(storagePool);
      String asyncTaskMessageId = null;

      // isThinVolume = true, creates VirtualVolumes
      // isThinVolume = false, creates LogicalUnits
      if (isThinVolume) {
        asyncTaskMessageId =
            hdsApiClient.createThinVolumes(
                systemObjectID,
                storagePool.getNativeId(),
                capacity,
                volumes.size(),
                label,
                QUICK_FORMAT_TYPE,
                storageSystem.getModel());
      } else if (!isThinVolume) {
        asyncTaskMessageId =
            hdsApiClient.createThickVolumes(
                systemObjectID,
                poolObjectID,
                capacity,
                volumes.size(),
                label,
                null,
                storageSystem.getModel(),
                null);
      }

      if (asyncTaskMessageId != null) {
        HDSJob createHDSJob =
            (volumes.size() > 1)
                ? new HDSCreateMultiVolumeJob(
                    asyncTaskMessageId,
                    volumes.get(0).getStorageController(),
                    storagePool.getId(),
                    volumes.size(),
                    taskCompleter)
                : new HDSCreateVolumeJob(
                    asyncTaskMessageId,
                    volumes.get(0).getStorageController(),
                    storagePool.getId(),
                    taskCompleter);
        ControllerServiceImpl.enqueueJob(new QueueJob(createHDSJob));
      }
    } catch (final InternalException e) {
      log.error("Problem in doCreateVolumes: ", e);
      opCreationFailed = true;
      taskCompleter.error(dbClient, e);
    } catch (final Exception e) {
      log.error("Problem in doCreateVolumes: ", e);
      opCreationFailed = true;
      ServiceError serviceError =
          DeviceControllerErrors.hds.methodFailed("doCreateVolumes", e.getMessage());
      taskCompleter.error(dbClient, serviceError);
    }
    if (opCreationFailed) {
      for (Volume vol : volumes) {
        vol.setInactive(true);
        dbClient.persistObject(vol);
      }
    }

    logMsgBuilder =
        new StringBuilder(
            String.format(
                "Create Volumes End - Array:%s, Pool:%s",
                storageSystem.getSerialNumber(), storagePool.getNativeGuid()));
    for (Volume volume : volumes) {
      logMsgBuilder.append(String.format("%nVolume:%s", volume.getLabel()));
    }
    log.info(logMsgBuilder.toString());
  }
  @Override
  public void doModifyVolumes(
      StorageSystem storage,
      StoragePool storagePool,
      String opId,
      List<Volume> volumes,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {
    StringBuilder logMsgBuilder =
        new StringBuilder(
            String.format(
                "Modify Volume Start - Array:%s, Pool:%s",
                storage.getSerialNumber(), storagePool.getNativeGuid()));

    String systemObjectID = HDSUtils.getSystemObjectID(storage);
    for (Volume volume : volumes) {
      try {
        HDSApiClient hdsApiClient =
            hdsApiFactory.getClient(
                HDSUtils.getHDSServerManagementServerInfo(storage),
                storage.getSmisUserName(),
                storage.getSmisPassword());
        logMsgBuilder.append(
            String.format(
                "%nVolume:%s , IsThinlyProvisioned: %s, tieringPolicy: %s",
                volume.getLabel(),
                volume.getThinlyProvisioned(),
                volume.getAutoTieringPolicyUri()));
        LogicalUnit logicalUnit =
            hdsApiClient.getLogicalUnitInfo(
                systemObjectID, HDSUtils.getLogicalUnitObjectId(volume.getNativeId(), storage));
        String policyName = ControllerUtils.getAutoTieringPolicyName(volume.getId(), dbClient);
        String autoTierPolicyName = null;
        if (policyName.equals(Constants.NONE)) {
          autoTierPolicyName = null;
        } else {
          autoTierPolicyName =
              HitachiTieringPolicy.getPolicy(
                      policyName.replaceAll(
                          HDSConstants.SLASH_OPERATOR, HDSConstants.UNDERSCORE_OPERATOR))
                  .getKey();
        }
        if (null != logicalUnit
            && null != logicalUnit.getLdevList()
            && !logicalUnit.getLdevList().isEmpty()) {
          Iterator<LDEV> ldevItr = logicalUnit.getLdevList().iterator();
          if (ldevItr.hasNext()) {
            LDEV ldev = ldevItr.next();
            String asyncMessageId =
                hdsApiClient.modifyThinVolumeTieringPolicy(
                    systemObjectID,
                    logicalUnit.getObjectID(),
                    ldev.getObjectID(),
                    autoTierPolicyName);
            if (null != asyncMessageId) {
              HDSJob modifyHDSJob =
                  new HDSModifyVolumeJob(
                      asyncMessageId,
                      volume.getStorageController(),
                      taskCompleter,
                      HDSModifyVolumeJob.VOLUME_MODIFY_JOB);
              ControllerServiceImpl.enqueueJob(new QueueJob(modifyHDSJob));
            }
          }
        } else {
          String errorMsg = String.format("No LDEV's found for volume: %s", volume.getId());
          log.info(errorMsg);
          ServiceError serviceError =
              DeviceControllerErrors.hds.methodFailed("doModifyVolumes", errorMsg);
          taskCompleter.error(dbClient, serviceError);
        }
      } catch (final InternalException e) {
        log.error("Problem in doModifyVolumes: ", e);
        taskCompleter.error(dbClient, e);
      } catch (final Exception e) {
        log.error("Problem in doModifyVolumes: ", e);
        ServiceError serviceError =
            DeviceControllerErrors.hds.methodFailed("doModifyVolumes", e.getMessage());
        taskCompleter.error(dbClient, serviceError);
      }
    }
  }