package com.thinkbiganalytics.datalake.authorization.hdfs;

/*-
 * #%L
 * thinkbig-sentry-client
 * %%
 * Copyright (C) 2017 ThinkBig Analytics
 * %%
 * 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.
 * #L%
 */


import com.google.common.collect.Lists;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclEntryScope;
import org.apache.hadoop.fs.permission.AclEntryType;
import org.apache.hadoop.fs.permission.FsAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;

/**
 */

public class HDFSUtil {

    private static final Logger log = LoggerFactory.getLogger(HDFSUtil.class);
    final private static String READ = "read";
    final private static String WRITE = "write";
    final private static String EXECUTE = "execute";
    final private static String ALL = "all";
    final private static String NONE = "none";
    private static String modelDBPath = "/model.db/";
    private static String appBasePath = "/app/warehouse/";
    private static String etlBasePath = "/etl/";
    private static String archiveBasePath = "/archive/";

    /**
     * @param configResources : Hadoop configuration resource
     */
    public static Configuration getConfigurationFromResources(String configResources) throws IOException {
        boolean foundResources = false;
        final Configuration config = new Configuration();
        if (null != configResources) {
            String[] resources = configResources.split(",");
            for (String resource : resources) {
                config.addResource(new Path(resource.trim()));
                foundResources = true;
            }
        }

        if (!foundResources) {
            // check that at least 1 non-default resource is available on the classpath
            String configStr = config.toString();
            for (String resource : configStr.substring(configStr.indexOf(":") + 1).split(",")) {
                if (!resource.contains("default") && config.getResource(resource.trim()) != null) {
                    foundResources = true;
                    break;
                }
            }
        }

        if (!foundResources) {
            throw new IOException("Could not find any of the " + "hadoop conf" + " on the classpath");
        }
        return config;
    }

    /**
     * @param category_name    : Category Name
     * @param feed_name        : Feed Name
     * @param permission_level : Level At Which Permission Needs to be granted.
     * @return : Returns list of HDFS of Path
     */
    public String constructResourceforPermissionHDFS(String category_name, String feed_name, String permission_level) {
        String final_resource_path = "";

        //Check level at which permission needs to be defined.
        if (permission_level.equalsIgnoreCase("category")) {
            String modeldb = modelDBPath + category_name;
            String appPath = appBasePath + category_name;
            String etlPath = etlBasePath + category_name;
            String archivePath = archiveBasePath + category_name;
            final_resource_path = modeldb + "," + appPath + "," + etlPath + "," + archivePath;
        } else {
            String modeldb = modelDBPath + category_name + "/" + feed_name;
            String appPath = appBasePath + category_name + "/" + feed_name;
            String etlPath = etlBasePath + category_name + "/" + feed_name;
            String archivePath = archiveBasePath + category_name + "/" + feed_name;
            final_resource_path = modeldb + "," + appPath + "," + etlPath + "," + archivePath;
        }
        return final_resource_path;
    }

    /**
     * @param allPathForAclCreation : Each Kylo internal path
     * @param conf                  : Hadoop configuration resources
     * @param fileSystem            : HDFS filesystem
     * @param groupList             : List of group for granting permission
     */
    public void splitPathListAndApplyPolicy(String allPathForAclCreation, Configuration conf, FileSystem fileSystem, String groupList, String hdfs_permission) throws IOException {
        String allKyloIntermediatePath[] = allPathForAclCreation.split(",");
        for (int pathCounter = 0; pathCounter < allKyloIntermediatePath.length; pathCounter++) {
            try {
                individualIntermediatePathApplyPolicy(conf, fileSystem, allKyloIntermediatePath[pathCounter], groupList, hdfs_permission);
            } catch (IOException e) {
                throw new IOException("Unable to iterate on HDFS directories " + e.getMessage());
            }
        }
    }

    public void splitPathListAndFlushPolicy(String allPathForAclCreation, Configuration conf, FileSystem fileSystem) throws IOException {
        String allKyloIntermediatePath[] = allPathForAclCreation.split(",");
        for (int pathCounter = 0; pathCounter < allKyloIntermediatePath.length; pathCounter++) {
            try {
                individualIntermediatePathFlushPolicy(conf, fileSystem, allKyloIntermediatePath[pathCounter]);
            } catch (IOException e) {
                throw new IOException("Unable to iterate on HDFS directories " + e.getMessage());
            }
        }
    }

    public void individualIntermediatePathApplyPolicy(Configuration conf, FileSystem fileSystem, String kyloPath, String groupList, String hdfs_permission) throws IOException {
        Path path = new Path(kyloPath);
        fileSystem = path.getFileSystem(conf);
        listAllDirAndApplyPolicy(fileSystem, path, groupList, hdfs_permission);
    }

    public void individualIntermediatePathFlushPolicy(Configuration conf, FileSystem fileSystem, String kyloPath) throws IOException {
        Path path = new Path(kyloPath);
        fileSystem = path.getFileSystem(conf);
        listAllDirAndFlushPolicy(fileSystem, path);
    }


    private void listAllDirAndFlushPolicy(FileSystem fileSystem, Path path) throws FileNotFoundException, IOException {

        FileStatus[] fileStatus = fileSystem.listStatus(path);

        for (FileStatus status : fileStatus) {

            // Apply ACL recursively on each file/directory.
            if (status.isDirectory()) {

                // Flush ACL before creating new one.
                flushAcl(fileSystem, status.getPath());

                listAllDirAndFlushPolicy(fileSystem, status.getPath());
            } else {

                // Flush ACL before creating new one.
                flushAcl(fileSystem, status.getPath());
            }
        }
    }


    /**
     * @param fileSystem : HDFS fileSystem object
     * @param path       : Path on which ACL needs to be created
     * @param groups     : List of group to which permission needs to be granted.
     */

    public void listAllDirAndApplyPolicy(FileSystem fileSystem, Path path, String groups, String hdfsPermission) throws FileNotFoundException, IOException {
        FsAction fsActionObject = getFinalPermission(hdfsPermission);
        FileStatus[] fileStatus = fileSystem.listStatus(path);

        for (FileStatus status : fileStatus) {

            // Flush ACL before creating new one.
            flushAcl(fileSystem, status.getPath());

            // Apply ACL recursively on each file/directory.
            if (status.isDirectory()) {
                String groupListForPermission[] = groups.split(",");
                for (int groupCounter = 0; groupCounter < groupListForPermission.length; groupCounter++) {

                    // Create HDFS ACL for each for each Path on HDFS
                    AclEntry aclEntryOwner = new AclEntry.Builder().setName(groupListForPermission[groupCounter])
                        .setPermission(fsActionObject).setScope(AclEntryScope.ACCESS).setType(AclEntryType.GROUP).build();

                    AclEntry aclEntryOther = new AclEntry.Builder().setPermission(FsAction.NONE).setScope(AclEntryScope.ACCESS).setType(AclEntryType.OTHER).build();

                    // Apply ACL on Path
                    applyAcl(fileSystem, status.getPath(), aclEntryOwner);
                    applyAcl(fileSystem, status.getPath(), aclEntryOther);

                }

                // Recursive call made to apply acl on each sub directory
                listAllDirAndApplyPolicy(fileSystem, status.getPath(), groups, hdfsPermission);
            } else {
                String groupListForPermission[] = groups.split(",");
                for (int groupCounter = 0; groupCounter < groupListForPermission.length; groupCounter++) {

                    // Create HDFS ACL for each for each Path on HDFS
                    AclEntry aclEntryOwner = new AclEntry.Builder().setName(groupListForPermission[groupCounter])
                        .setPermission(fsActionObject).setScope(AclEntryScope.ACCESS).setType(AclEntryType.GROUP).build();

                    AclEntry aclEntryOther = new AclEntry.Builder().setPermission(FsAction.NONE).setScope(AclEntryScope.ACCESS).setType(AclEntryType.OTHER).build();

                    // Apply ACL on Path
                    applyAcl(fileSystem, status.getPath(), aclEntryOwner);
                    applyAcl(fileSystem, status.getPath(), aclEntryOther);

                }
            }
        }
    }

    /**
     * @param fileSystem : HDFS FileSystem Object
     * @param path       : HDFS Path
     * @param aclEntry   : ACL for HDFS Path
     */
    public void applyAcl(FileSystem fileSystem, Path path, AclEntry aclEntry) throws IOException {
        try {
            fileSystem.modifyAclEntries(path, Lists.newArrayList(aclEntry));
        } catch (IOException e) {
            throw new IOException("Unable to apply HDFS Policy for " + path.toString() + " " + e.getMessage());
        }
    }

    /**
     * @param fileSystem : HDFS FileSystem Object
     * @param path       : HDFS Path
     */
    public void flushAcl(FileSystem fileSystem, Path path) throws IOException {
        try {
            fileSystem.removeAcl(path);
        } catch (IOException e) {
            throw new IOException("Unable to flush HDFS Policy for " + path.toString() + " " + e.getMessage());
        }

    }

    /**
     * @param hdfsPermission : Permission assgined by user.
     * @return : Final Permission to be set for creating ACL
     */

    private FsAction getFinalPermission(String hdfsPermission) {

        HashMap<String, Integer> standardPermissionMap = new HashMap<String, Integer>();

        String permissions[] = hdfsPermission.split(",");
        standardPermissionMap.put(READ, 0);
        standardPermissionMap.put(WRITE, 0);
        standardPermissionMap.put(EXECUTE, 0);
        standardPermissionMap.put(NONE, 0);
        standardPermissionMap.put(ALL, 0);

        for (String permission : permissions) {

            permission = permission.toLowerCase();

            switch (permission) {
                case READ:
                    standardPermissionMap.put(READ, 1);
                    break;
                case WRITE:
                    standardPermissionMap.put(WRITE, 1);
                    break;
                case EXECUTE:
                    standardPermissionMap.put(EXECUTE, 1);
                    break;
                case ALL:
                    return FsAction.ALL;
                case NONE:
                    return FsAction.NONE;
                default:
                    standardPermissionMap.put(NONE, 1);
            }
        }

        if (standardPermissionMap.get(READ) == 1 && standardPermissionMap.get(WRITE) == 1 && standardPermissionMap.get(EXECUTE) == 1) {
            return FsAction.ALL;
        }

        if (standardPermissionMap.get(READ) == 1 && standardPermissionMap.get(WRITE) == 1) {
            return FsAction.READ_WRITE;
        }

        if (standardPermissionMap.get(READ) == 1 && standardPermissionMap.get(EXECUTE) == 1) {
            return FsAction.READ_EXECUTE;
        }

        if (standardPermissionMap.get(WRITE) == 1 && standardPermissionMap.get(EXECUTE) == 1) {
            return FsAction.WRITE_EXECUTE;
        }

        if (standardPermissionMap.get(WRITE) == 1) {
            return FsAction.WRITE;
        }

        if (standardPermissionMap.get(READ) == 1) {
            return FsAction.READ;
        }

        if (standardPermissionMap.get(EXECUTE) == 1) {
            return FsAction.EXECUTE;
        }

        // Default Permission - None
        return FsAction.NONE;

    }
}
