/*
 * Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
 * license agreements.  See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.  Crate 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.
 *
 * However, if you have executed another commercial license agreement
 * with Crate these terms will supersede the license and you may use the
 * software solely pursuant to the terms of the relevant commercial agreement.
 */

package io.crate.blob;

import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.FileSystemUtils;
import org.elasticsearch.common.settings.SettingsException;
import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;

public class BlobEnvironment {

    public static final String SETTING_BLOBS_PATH = "blobs.path";
    public static final String BLOBS_SUB_PATH = "blobs";

    private final NodeEnvironment nodeEnvironment;
    private final ClusterName clusterName;

    @Nullable
    private File blobsPath;

    @Inject
    public BlobEnvironment(NodeEnvironment nodeEnvironment, ClusterName clusterName) {
        this.nodeEnvironment = nodeEnvironment;
        this.clusterName = clusterName;
    }

    @Nullable
    public File blobsPath() {
        return blobsPath;
    }

    public void blobsPath(File blobPath) {
        validateBlobsPath(blobPath);
        this.blobsPath = blobPath;
    }

    /**
     * Return the index location respecting global blobs data path value
     */
    public File indexLocation(Index index) {
        if (blobsPath == null) {
            return nodeEnvironment.indexPaths(index)[0].toFile();
        }
        return indexLocation(index, blobsPath);
    }

    /**
     * Return the index location according to the given base path
     */
    public File indexLocation(Index index, File path) {
        File indexLocation = nodeEnvironment.indexPaths(index)[0].toFile();
        String dataPath = nodeEnvironment.nodeDataPaths()[0].toString();
        String indexLocationSuffix = indexLocation.getAbsolutePath().substring(dataPath.length());
        return new File(path, indexLocationSuffix);
    }

    /**
     * Return the shard location respecting global blobs data path value
     */
    public File shardLocation(ShardId shardId) {
        if (blobsPath == null) {
            return new File(nodeEnvironment.availableShardPaths(shardId)[0].toFile(), BLOBS_SUB_PATH);
        }
        return shardLocation(shardId, blobsPath);
    }

    /**
     * Return the shard location according to the given base path
     *
     */
    public File shardLocation(ShardId shardId, File path) {
        Path shardLocation = nodeEnvironment.availableShardPaths(shardId)[0];
        Path dataPath = nodeEnvironment.nodeDataPaths()[0];
        String shardLocationSuffix = shardLocation.toAbsolutePath().toString().substring(dataPath.toString().length());
        return new File(new File(path, shardLocationSuffix), BLOBS_SUB_PATH);
    }

    /**
     * Validates a given blobs data path
     */
    public void validateBlobsPath(File blobsPath) {
        if (blobsPath.exists()) {
            if (blobsPath.isFile()) {
                throw new SettingsException(
                        String.format(Locale.ENGLISH, "blobs path '%s' is a file, must be a directory", blobsPath.getAbsolutePath()));
            }
            if (!blobsPath.canWrite()) {
                throw new SettingsException(
                        String.format(Locale.ENGLISH, "blobs path '%s' is not writable", blobsPath.getAbsolutePath()));
            }
        } else {
            try {
                Files.createDirectories(blobsPath.toPath());
            } catch (IOException e) {
                throw new SettingsException(
                        String.format(Locale.ENGLISH, "blobs path '%s' could not be created", blobsPath.getAbsolutePath()));
            }
        }
    }

    /**
     * Check if a given blob data path contains no indices and non crate related path
     */
    public boolean isCustomBlobPathEmpty(File root) {
        return isCustomBlobPathEmpty(root, true);
    }

    private boolean isCustomBlobPathEmpty(File file, boolean isRoot) {
        if (file == null || !file.exists() || !file.isDirectory()) {
            return false;
        }
        File[] children = file.listFiles();
        if (children == null || children.length == 0) {
            return true;
        }
        //noinspection SimplifiableIfStatement
        if (isRoot && children.length == 1 && children[0].getName().equals("indices")) {
            return isCustomBlobPathEmpty(children[0], false);
        }
        return false;
    }
}