/*
   * (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");
  }
  /**
   * Gets the VPlex cluster Id for a given VPlex virtual volume.
   *
   * @param virtualVolume
   * @return
   */
  private String getVPlexClusterFromVolume(Volume virtualVolume) {
    String clusterId = null;

    if (virtualVolume != null && virtualVolume.getNativeId() != null) {
      String[] nativeIdSplit = virtualVolume.getNativeId().split("/");
      clusterId = nativeIdSplit[2];
    }

    return clusterId;
  }
  private void handleBlockVolumeErrors(DbClient dbClient) {
    for (VolumeDescriptor volumeDescriptor :
        VolumeDescriptor.getDescriptors(_volumeDescriptors, VolumeDescriptor.Type.BLOCK_DATA)) {

      Volume volume = dbClient.queryObject(Volume.class, volumeDescriptor.getVolumeURI());

      if (volume != null && (volume.getNativeId() == null || volume.getNativeId().equals(""))) {
        _log.info("No native id was present on volume {}, marking inactive", volume.getLabel());
        dbClient.markForDeletion(volume);
      }
    }
  }
  /*
   * (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()));
  }
  /**
   * Detaches volumes from initiators.
   *
   * @param storage the storage
   * @param volumes the volumes
   * @param initiators the initiators
   * @throws Exception the exception
   */
  private void detachVolumesFromInitiators(
      StorageSystem storage, List<Volume> volumes, List<Initiator> initiators) throws Exception {
    CinderEndPointInfo ep = CinderUtils.getCinderEndPoint(storage.getActiveProviderURI(), dbClient);
    log.debug("Getting the cinder APi for the provider with id {}", storage.getActiveProviderURI());
    CinderApi cinderApi = cinderApiFactory.getApi(storage.getActiveProviderURI(), ep);

    List<Initiator> iSCSIInitiators = new ArrayList<Initiator>();
    List<Initiator> fcInitiators = new ArrayList<Initiator>();
    splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators);
    String host = getHostNameFromInitiators(initiators);

    Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators);
    String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS);
    String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS);

    for (Volume volume : volumes) {
      // cinder generated volume ID
      String volumeId = volume.getNativeId();

      // for iSCSI
      for (Initiator initiator : iSCSIInitiators) {
        String initiatorPort = initiator.getInitiatorPort();
        log.debug(
            String.format(
                "Detaching volume %s ( %s ) from initiator %s on Openstack cinder node",
                volumeId, volume.getId(), initiatorPort));
        cinderApi.detachVolume(volumeId, initiatorPort, null, null, host);

        // TODO : Do not use Job to poll status till we figure out how
        // to get detach status.
        /*
         * CinderJob detachJob = new CinderDetachVolumeJob(volumeId,
         * volume.getLabel(), storage.getId(),
         * CinderConstants.ComponentType.volume.name(), ep,
         * taskCompleter); ControllerServiceImpl.enqueueJob(new
         * QueueJob(detachJob));
         */
      }

      // for FC
      if (fcInitiatorsWwpns.length > 0) {
        log.debug(
            String.format(
                "Detaching volume %s ( %s ) from initiator %s on Openstack cinder node",
                volumeId, volume.getId(), fcInitiatorsWwpns));
        cinderApi.detachVolume(volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host);
      }

      // If ITLs are added, remove them
      removeITLsFromVolume(volume);
    }
  }
  @Override
  public void createSingleVolumeSnapshot(
      StorageSystem storage,
      URI snapshot,
      Boolean createInactive,
      Boolean readOnly,
      TaskCompleter taskCompleter)
      throws DeviceControllerException {

    try {
      BlockSnapshot snapshotObj = _dbClient.queryObject(BlockSnapshot.class, snapshot);

      Volume volume = _dbClient.queryObject(Volume.class, snapshotObj.getParent());
      TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, volume.getTenant().getURI());
      String tenantName = tenant.getLabel();
      String snapLabelToUse =
          _nameGenerator.generate(
              tenantName,
              snapshotObj.getLabel(),
              snapshot.toString(),
              '-',
              SmisConstants.MAX_SNAPSHOT_NAME_LENGTH);

      VNXeApiClient apiClient = getVnxeClient(storage);
      VNXeCommandJob job = apiClient.createLunSnap(volume.getNativeId(), snapLabelToUse);
      if (job != null) {
        ControllerServiceImpl.enqueueJob(
            new QueueJob(
                new VNXeBlockSnapshotCreateJob(
                    job.getId(), storage.getId(), !createInactive, taskCompleter)));
      }
    } catch (VNXeException e) {
      _log.error("Create volume snapshot got the exception", e);
      taskCompleter.error(_dbClient, e);

    } catch (Exception ex) {
      _log.error("Create volume snapshot got the exception", ex);
      ServiceError error =
          DeviceControllerErrors.vnxe.jobFailed("CreateVolumeSnapshot", ex.getMessage());
      taskCompleter.error(_dbClient, error);
    }
  }
  /*
   * (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());
  }
  @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);
      }
    }
  }
  /**
   * Attaches volumes to initiators.
   *
   * @param storage the storage
   * @param volumes the volumes
   * @param initiators the initiators
   * @param volumeToTargetLunMap the volume to target lun map
   * @throws Exception the exception
   */
  private void attachVolumesToInitiators(
      StorageSystem storage,
      List<Volume> volumes,
      List<Initiator> initiators,
      Map<URI, Integer> volumeToTargetLunMap,
      Map<Volume, Map<String, List<String>>> volumeToInitiatorTargetMap,
      ExportMask exportMask)
      throws Exception {
    CinderEndPointInfo ep = CinderUtils.getCinderEndPoint(storage.getActiveProviderURI(), dbClient);
    log.debug(
        "Getting the cinder APi for the provider with id  {}", storage.getActiveProviderURI());
    CinderApi cinderApi = cinderApiFactory.getApi(storage.getActiveProviderURI(), ep);

    List<Initiator> iSCSIInitiators = new ArrayList<Initiator>();
    List<Initiator> fcInitiators = new ArrayList<Initiator>();
    splitInitiatorsByProtocol(initiators, iSCSIInitiators, fcInitiators);
    String host = getHostNameFromInitiators(initiators);

    Map<String, String[]> mapSettingVsValues = getFCInitiatorsArray(fcInitiators);
    String[] fcInitiatorsWwpns = mapSettingVsValues.get(WWPNS);
    String[] fcInitiatorsWwnns = mapSettingVsValues.get(WWNNS);

    for (Volume volume : volumes) {

      // cinder generated volume ID
      String volumeId = volume.getNativeId();
      int targetLunId = -1;
      VolumeAttachResponse attachResponse = null;

      // for iSCSI
      for (Initiator initiator : iSCSIInitiators) {
        String initiatorPort = initiator.getInitiatorPort();
        log.debug(
            String.format(
                "Attaching volume %s ( %s ) to initiator %s on Openstack cinder node",
                volumeId, volume.getId(), initiatorPort));
        attachResponse = cinderApi.attachVolume(volumeId, initiatorPort, null, null, host);
        log.info("Got response : {}", attachResponse.connection_info.toString());
        targetLunId = attachResponse.connection_info.data.target_lun;
      }

      // for FC
      if (fcInitiatorsWwpns.length > 0) {
        log.debug(
            String.format(
                "Attaching volume %s ( %s ) to initiators %s on Openstack cinder node",
                volumeId, volume.getId(), fcInitiatorsWwpns));
        attachResponse =
            cinderApi.attachVolume(volumeId, null, fcInitiatorsWwpns, fcInitiatorsWwnns, host);
        log.info("Got response : {}", attachResponse.connection_info.toString());
        targetLunId = attachResponse.connection_info.data.target_lun;

        Map<String, List<String>> initTargetMap =
            attachResponse.connection_info.data.initiator_target_map;
        if (null != initTargetMap && !initTargetMap.isEmpty()) {
          volumeToInitiatorTargetMap.put(
              volume, attachResponse.connection_info.data.initiator_target_map);
        }
      }

      volumeToTargetLunMap.put(volume.getId(), targetLunId);

      // After the successful export, create or modify the storage ports
      CinderStoragePortOperations storagePortOperationsInstance =
          CinderStoragePortOperations.getInstance(storage, dbClient);
      storagePortOperationsInstance.invoke(attachResponse);
    }

    // Add ITLs to volume objects
    storeITLMappingInVolume(volumeToTargetLunMap, exportMask);
  }