/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed 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 com.alibaba.nacos.api.naming.pojo;

import com.alibaba.nacos.api.common.Constants;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Service Information with instances and without cluster information, used in data pushing and cached for nacos-client.
 *
 * @author nkorange
 * @author shizhengxing
 */
@JsonInclude(Include.NON_NULL)
public class ServiceInfo implements Cloneable {
    
    /**
     * file name pattern: groupName@@name@@clusters.
     */
    private static final int GROUP_POSITION = 0;
    
    private static final int SERVICE_POSITION = 1;
    
    private static final int CLUSTER_POSITION = 2;
    
    private static final int FILE_NAME_PARTS = 3;
    
    @JsonIgnore
    private String jsonFromServer = EMPTY;
    
    private static final String EMPTY = "";
    
    private static final String DEFAULT_CHARSET = "UTF-8";
    
    private String name;
    
    private String groupName;
    
    private String clusters;
    
    private long cacheMillis = 1000L;
    
    private List<Instance> hosts = new ArrayList<>();
    
    private long lastRefTime = 0L;
    
    private String checksum = "";
    
    private volatile boolean allIps = false;
    
    private volatile boolean reachProtectionThreshold = false;
    
    public ServiceInfo() {
    }
    
    public boolean isAllIps() {
        return allIps;
    }
    
    public void setAllIps(boolean allIps) {
        this.allIps = allIps;
    }
    
    /**
     * There is only one form of the key:groupName@@name@@clusters. This constructor used by DiskCache.read(String) and
     * FailoverReactor.FailoverFileReader,you should know that 'groupName' must not be null,and 'clusters' can be null.
     */
    public ServiceInfo(final String key) {
        String[] keys = key.split(Constants.SERVICE_INFO_SPLITER);
        if (keys.length >= FILE_NAME_PARTS) {
            this.groupName = keys[GROUP_POSITION];
            this.name = keys[SERVICE_POSITION];
            this.clusters = keys[CLUSTER_POSITION];
        } else if (keys.length == CLUSTER_POSITION) {
            this.groupName = keys[GROUP_POSITION];
            this.name = keys[SERVICE_POSITION];
        } else {
            //defensive programming
            throw new IllegalArgumentException("Can't parse out 'groupName',but it must not be null!");
        }
    }
    
    public ServiceInfo(String name, String clusters) {
        this.name = name;
        this.clusters = clusters;
    }
    
    public int ipCount() {
        return hosts.size();
    }
    
    public boolean expired() {
        return System.currentTimeMillis() - lastRefTime > cacheMillis;
    }
    
    public void setHosts(List<Instance> hosts) {
        this.hosts = hosts;
    }
    
    public void addHost(Instance host) {
        hosts.add(host);
    }
    
    public void addAllHosts(List<? extends Instance> hosts) {
        this.hosts.addAll(hosts);
    }
    
    public List<Instance> getHosts() {
        return new ArrayList<>(hosts);
    }
    
    public boolean isValid() {
        return hosts != null;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getGroupName() {
        return groupName;
    }
    
    public void setGroupName(String groupName) {
        this.groupName = groupName;
    }
    
    public void setLastRefTime(long lastRefTime) {
        this.lastRefTime = lastRefTime;
    }
    
    public long getLastRefTime() {
        return lastRefTime;
    }
    
    public String getClusters() {
        return clusters;
    }
    
    public void setClusters(String clusters) {
        this.clusters = clusters;
    }
    
    public long getCacheMillis() {
        return cacheMillis;
    }
    
    public void setCacheMillis(long cacheMillis) {
        this.cacheMillis = cacheMillis;
    }
    
    /**
     * Judge whether service info is validate.
     *
     * @return true if validate, otherwise false
     */
    public boolean validate() {
        if (isAllIps()) {
            return true;
        }
        
        if (hosts == null) {
            return false;
        }
        
        boolean existValidHosts = false;
        for (Instance host : hosts) {
            if (host.isHealthy() && host.getWeight() > 0) {
                existValidHosts = true;
                break;
            }
        }
        return existValidHosts;
    }
    
    @JsonIgnore
    public String getJsonFromServer() {
        return jsonFromServer;
    }
    
    public void setJsonFromServer(String jsonFromServer) {
        this.jsonFromServer = jsonFromServer;
    }
    
    @JsonIgnore
    public String getKey() {
        String serviceName = getGroupedServiceName();
        return getKey(serviceName, clusters);
    }
    
    @JsonIgnore
    public static String getKey(String name, String clusters) {
        if (!isEmpty(clusters)) {
            return name + Constants.SERVICE_INFO_SPLITER + clusters;
        }
        return name;
    }

    @JsonIgnore
    public String getKeyWithoutClusters() {
        return getGroupedServiceName();
    }

    @JsonIgnore
    public String getKeyEncoded() {
        String serviceName = getGroupedServiceName();
        try {
            serviceName = URLEncoder.encode(serviceName, DEFAULT_CHARSET);
        } catch (UnsupportedEncodingException ignored) {
        }
        return getKey(serviceName, clusters);
    }
    
    private String getGroupedServiceName() {
        String serviceName = this.name;
        if (!isEmpty(groupName) && !serviceName.contains(Constants.SERVICE_INFO_SPLITER)) {
            serviceName = groupName + Constants.SERVICE_INFO_SPLITER + serviceName;
        }
        return serviceName;
    }
    
    /**
     * Get {@link ServiceInfo} from key.
     *
     * @param key key of service info
     * @return new service info
     */
    public static ServiceInfo fromKey(final String key) {
        return new ServiceInfo(key);
    }
    
    @Override
    public String toString() {
        return getKey();
    }
    
    public String getChecksum() {
        return checksum;
    }
    
    public void setChecksum(String checksum) {
        this.checksum = checksum;
    }
    
    private static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
    
    public boolean isReachProtectionThreshold() {
        return reachProtectionThreshold;
    }
    
    public void setReachProtectionThreshold(boolean reachProtectionThreshold) {
        this.reachProtectionThreshold = reachProtectionThreshold;
    }
    
    @Override
    public ServiceInfo clone() {
        ServiceInfo cloned = new ServiceInfo();
        cloned.jsonFromServer = this.jsonFromServer;
        cloned.name = this.name;
        cloned.groupName = this.groupName;
        cloned.clusters = this.clusters;
        cloned.cacheMillis = this.cacheMillis;
        cloned.lastRefTime = this.lastRefTime;
        cloned.checksum = this.checksum;
        cloned.allIps = this.allIps;
        cloned.reachProtectionThreshold = this.reachProtectionThreshold;
        cloned.hosts = new ArrayList<>();
        
        if (this.hosts != null) {
            for (Instance host : this.hosts) {
                Instance clonedHost = new Instance();
                clonedHost.setInstanceId(host.getInstanceId());
                clonedHost.setIp(host.getIp());
                clonedHost.setPort(host.getPort());
                clonedHost.setWeight(host.getWeight());
                clonedHost.setHealthy(host.isHealthy());
                clonedHost.setEnabled(host.isEnabled());
                clonedHost.setEphemeral(host.isEphemeral());
                clonedHost.setClusterName(host.getClusterName());
                clonedHost.setServiceName(host.getServiceName());
                
                if (host.getMetadata() != null) {
                    Map<String, String> clonedMetadata = new HashMap<>(host.getMetadata());
                    clonedHost.setMetadata(clonedMetadata);
                }
                
                cloned.hosts.add(clonedHost);
            }
        }
        
        return cloned;
    }
}
