// 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.cloudstack.storage.datastore.util;

import com.google.gson.annotations.SerializedName;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

public class DateraObject {

    public static final String DEFAULT_CREATE_MODE = "cloudstack";
    public static final String DEFAULT_STORAGE_NAME = "storage-1";
    public static final String DEFAULT_VOLUME_NAME = "volume-1";
    public static final String DEFAULT_ACL = "deny_all";
    public static final String DEFAULT_STORAGE_FORCE_BOOLEAN = "true";

    public enum AppState {
        ONLINE, OFFLINE;

        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    public enum DateraOperation {
        ADD, REMOVE;

        @Override
        public String toString() {
            return this.name().toLowerCase();
        }
    }

    public enum DateraErrorTypes {
        PermissionDeniedError, InvalidRouteError, AuthFailedError, ValidationFailedError, InvalidRequestError,
        NotFoundError, NotConnectedError, InvalidSessionKeyError, DatabaseError, InternalError,ConflictError;

        public boolean equals(DateraError err) {
            return this.name().equals(err.getName());
        }
    }

    public static class DateraConnection {

        private int managementPort;
        private String managementIp;
        private String username;
        private String password;

        public DateraConnection(String managementIp, int managementPort, String username, String password) {
            this.managementPort = managementPort;
            this.managementIp = managementIp;
            this.username = username;
            this.password = password;
        }

        public int getManagementPort() {
            return managementPort;
        }

        public String getManagementIp() {
            return managementIp;
        }

        public String getUsername() {
            return username;
        }

        public String getPassword() {
            return password;
        }
    }

    public static class DateraLogin {

        private final String name;
        private final String password;

        public DateraLogin(String username, String password) {
            this.name = username;
            this.password = password;
        }
    }

    public static class DateraLoginResponse {

        private String key;

        public String getKey() {
            return key;
        }
    }

    public class Access {
        private String iqn;
        private List<String> ips;

        public Access(String iqn, List<String> ips) {
            this.iqn = iqn;
            this.ips = ips;
        }

        public String getIqn() {
            return iqn;
        }
    }

    public static class PerformancePolicy {

        @SerializedName("total_iops_max")
        private Integer totalIops;

        public PerformancePolicy(int totalIops) {
            this.totalIops = totalIops;
        }

        public Integer getTotalIops() {
            return totalIops;
        }
    }

    public static class Volume {

        private String name;
        private String path;
        private Integer size;

        @SerializedName("replica_count")
        private Integer replicaCount;

        @SerializedName("performance_policy")
        private PerformancePolicy performancePolicy;

        @SerializedName("placement_mode")
        private String placementMode;

        @SerializedName("op_state")
        private String opState;

        public Volume(int size, int totalIops, int replicaCount) {
            this.name = DEFAULT_VOLUME_NAME;
            this.size = size;
            this.replicaCount = replicaCount;
            this.performancePolicy = new PerformancePolicy(totalIops);
        }

        public Volume(int size, int totalIops, int replicaCount, String placementMode) {
            this.name = DEFAULT_VOLUME_NAME;
            this.size = size;
            this.replicaCount = replicaCount;
            this.performancePolicy = new PerformancePolicy(totalIops);
            this.placementMode = placementMode;
        }

        public Volume(Integer newSize) {
            this.size = newSize;
        }

        public Volume(String newPlacementMode) {
            this.placementMode = newPlacementMode;
        }

        public PerformancePolicy getPerformancePolicy() {
            return performancePolicy;
        }

        public int getSize() {
            return size;
        }

        public String getPlacementMode() {
            return placementMode;
        }

        public String getPath() {
            return path;
        }

        public String getOpState() {
            return opState;
        }
    }

    public static class StorageInstance {

        private final String name = DEFAULT_STORAGE_NAME;
        private Map<String, Volume> volumes;
        private Access access;
        private String force;

        @SerializedName("ip_pool")
        private String ipPool;

        public StorageInstance(int size, int totalIops, int replicaCount) {
            Volume volume = new Volume(size, totalIops, replicaCount);
            volumes = new HashMap<String, Volume>();
            volumes.put(DEFAULT_VOLUME_NAME, volume);
        }

        public StorageInstance(int size, int totalIops, int replicaCount, String placementMode, String ipPool) {
            Volume volume = new Volume(size, totalIops, replicaCount, placementMode);
            volumes = new HashMap<String, Volume>();
            volumes.put(DEFAULT_VOLUME_NAME, volume);
            this.ipPool = new StringBuilder("/access_network_ip_pools/").append(ipPool).toString();
        }

        public StorageInstance(int size, int totalIops, int replicaCount, String placementMode, String ipPool, String force) {
            Volume volume = new Volume(size, totalIops, replicaCount, placementMode);
            volumes = new HashMap<String, Volume>();
            volumes.put(DEFAULT_VOLUME_NAME, volume);
            this.ipPool = new StringBuilder("/access_network_ip_pools/").append(ipPool).toString();
            this.force = DEFAULT_STORAGE_FORCE_BOOLEAN;
        }

        public Access getAccess() {
            return access;
        }

        public Volume getVolume() {
            return volumes.get(DEFAULT_VOLUME_NAME);
        }

        public int getSize() {
            return getVolume().getSize();
        }

        public String getForce() {
            return this.force;
        }

    }

    public static class AppInstance {

        private String name;

        @SerializedName("access_control_mode")
        private String accessControlMode;

        @SerializedName("create_mode")
        private String createMode;

        @SerializedName("storage_instances")
        private Map<String, StorageInstance> storageInstances;

        @SerializedName("clone_src")
        private String cloneSrc;

        @SerializedName("admin_state")
        private String adminState;
        private Boolean force;

        public AppInstance(String name, int size, int totalIops, int replicaCount) {
            this.name = name;
            StorageInstance storageInstance = new StorageInstance(size, totalIops, replicaCount);
            this.storageInstances = new HashMap<String, StorageInstance>();
            this.storageInstances.put(DEFAULT_STORAGE_NAME, storageInstance);
            this.accessControlMode = DEFAULT_ACL;
            this.createMode = DEFAULT_CREATE_MODE;
        }

        public AppInstance(String name, int size, int totalIops, int replicaCount, String placementMode,
                String ipPool) {
            this.name = name;
            StorageInstance storageInstance = new StorageInstance(size, totalIops, replicaCount, placementMode, ipPool);
            this.storageInstances = new HashMap<String, StorageInstance>();
            this.storageInstances.put(DEFAULT_STORAGE_NAME, storageInstance);
            this.accessControlMode = DEFAULT_ACL;
            this.createMode = DEFAULT_CREATE_MODE;
        }

        public AppInstance(AppState state) {
            this.adminState = state.toString();
            this.force = true;
        }

        public AppInstance(String name, String cloneSrc) {
            this.name = name;
            this.cloneSrc = cloneSrc;
        }

        public String getIqn() {
            StorageInstance storageInstance = storageInstances.get(DEFAULT_STORAGE_NAME);
            return storageInstance.getAccess().getIqn();
        }

        public int getTotalIops() {
            StorageInstance storageInstance = storageInstances.get(DEFAULT_STORAGE_NAME);
            return storageInstance.getVolume().getPerformancePolicy().getTotalIops();
        }

        public String getName() {
            return name;
        }

        public int getSize() {
            StorageInstance storageInstance = storageInstances.get(DEFAULT_STORAGE_NAME);
            return storageInstance.getSize();
        }

        public String getVolumePath() {
            StorageInstance storageInstance = storageInstances.get(DEFAULT_STORAGE_NAME);
            return storageInstance.getVolume().getPath();
        }

        public String getVolumeOpState() {
            StorageInstance storageInstance = storageInstances.get(DEFAULT_STORAGE_NAME);
            return storageInstance.getVolume().getOpState();
        }
    }

    public static class AccessNetworkIpPool {
        @SerializedName("ip_pool")
        private String ipPool;

        public AccessNetworkIpPool(String ipPool) {
            this.ipPool = new StringBuilder("/access_network_ip_pools/").append(ipPool).toString();
        }
    }

    public static class Initiator {

        private String id; // IQN
        private String name;
        private String path;
        private String op;

        public Initiator(String name, String id) {
            this.id = id;
            this.name = name;
        }

        public Initiator(String path, DateraOperation op) {
            this.path = path;
            this.op = op.toString();
        }

        public String getPath() {
            return path;
        }
    }

    public static class InitiatorGroup {

        private String name;
        private List<String> members;
        private String path;
        private String op;

        public InitiatorGroup(String name, List<String> members) {
            this.name = name;
            this.members = members;
        }

        public InitiatorGroup(String path, DateraOperation op) {
            this.path = path;
            this.op = op.toString();
        }

        public String getPath() {
            return path;
        }

        public String getName() {
            return name;
        }

        public List<String> getMembers() {
            return members;
        }
    }

    public static class VolumeSnapshot {

        private String uuid;
        private String timestamp;
        private String path;

        @SerializedName("op_state")
        private String opState;

        VolumeSnapshot(String uuid) {
            this.uuid = uuid;
        }

        public String getTimestamp() {
            return timestamp;
        }

        public String getOpState() {
            return opState;
        }

        public String getPath() {
            return path;
        }
    }

    public static class VolumeSnapshotRestore {

        @SerializedName("restore_point")
        private String restorePoint;

        VolumeSnapshotRestore(String restorePoint) {
            this.restorePoint = restorePoint;
        }
    }

    public static class DateraError extends Exception {

        private String name;
        private int code;
        private List<String> errors;
        private String message;

        public DateraError(String name, int code, List<String> errors, String message) {
            this.name = name;
            this.code = code;
            this.errors = errors;
            this.message = message;
        }

        public List<String> getErrors() {
            return errors;
        }

        public boolean isError() {
            return message != null && name.endsWith("Error");
        }

        public String getMessage() {

            String errMesg = name + "\n";
            if (message != null) {
                errMesg += message + "\n";
            }

            if (errors != null) {
                errMesg += StringUtils.join(errors, "\n");

            }

            return errMesg;
        }

        public String getName() {
            return name;
        }
    }
}
