/*
 * Copyright (C) 2014 Indeed Inc.
 *
 * 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.indeed.imhotep.io.caching;

import com.indeed.util.core.io.Closeables2;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HDFSRemoteFileSystem extends RemoteFileSystem {
    private static final Logger log = Logger.getLogger(HDFSRemoteFileSystem.class);

    private final RemoteFileSystemMounter mounter;
    private final String mountPoint;
    private final Path hdfsBasePath;
    private final FileSystem fs;

    HDFSRemoteFileSystem(Map<String,Object> settings, RemoteFileSystemMounter mounter)
            throws IOException {
        this.mounter = mounter;

        try {
            mountPoint = (String) settings.get("mountpoint");
            if (mountPoint != null) {
                hdfsBasePath = new Path(new URI(mountPoint));
            } else {
                hdfsBasePath = new Path("/var/imhotep/");
            }

            String kerbPrincipal = (String) settings.get("kerberos.principal");
            String kerbKeytab = (String) settings.get("kerberos.keytab");
            KerberosUtils.loginFromKeytab(kerbPrincipal, kerbKeytab);

            fs = FileSystem.get(new Configuration());
            log.info("Using path " + hdfsBasePath + " on file system: " + fs);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void copyFileInto(String fullPath, File localFile) throws IOException {
        final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
        final Path hdfsPath = new Path(hdfsBasePath, relativePath);
        fs.copyToLocalFile(hdfsPath, new Path(localFile.getPath()));
    }

    @Override
    public File loadFile(String fullPath) throws IOException {
        final File file = File.createTempFile("imhotep.hdfs.", ".cachedFile");
        copyFileInto(fullPath, file);
        return file;
    }

    @Override
    public RemoteFileInfo stat(String fullPath) {
        final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
        final Path hdfsPath = new Path(hdfsBasePath, relativePath);
        try {
            final FileStatus status = fs.getFileStatus(hdfsPath);
            if (status.isDirectory()) {
                return new RemoteFileInfo(relativePath, RemoteFileInfo.TYPE_DIR);
            } else {
                return new RemoteFileInfo(relativePath, RemoteFileInfo.TYPE_FILE);
            }
        } catch (final IOException e) {
            log.error(e);
            return null;
        }
    }

    @Override
    public List<RemoteFileInfo> readDir(String fullPath) {
        final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
        final Path hdfsPath = new Path(hdfsBasePath, relativePath);

        try {
            final FileStatus[] contents = fs.listStatus(hdfsPath);

            final List<RemoteFileInfo> results = new ArrayList<>();
            for (FileStatus status : contents) {
                if (status.getPath().equals(hdfsPath) && !status.isDirectory()) {
                    throw new IOException("Not a directory: " + fullPath);
                }
                if (status.isDirectory()) {
                    results.add(new RemoteFileInfo(status.getPath().getName(), RemoteFileInfo.TYPE_DIR));
                } else {
                    results.add(new RemoteFileInfo(status.getPath().getName(), RemoteFileInfo.TYPE_FILE));
                }
            }
            return results;
        } catch (IOException e) {
            log.error(e);
        }
        return null;
    }

    @Override
    public String getMountPoint() {
        return mountPoint;
    }

    @Override
    public Map<String, File> loadDirectory(String fullPath, File location) throws IOException {
        final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
        final Path hdfsPath = new Path(hdfsBasePath, relativePath);

        final Map<String, File> results = new HashMap<>(100);

        if (location == null) {
            location = File.createTempFile("hdfs", "remoteFile");
            location.delete();
            location.mkdir();
        }

        try {
            final FileStatus[] contents = fs.listStatus(hdfsPath);
            for (FileStatus status : contents) {
                if (!status.isDirectory()) {
                    File localFile = new File(location, status.getPath().getName());
                    localFile.getParentFile().mkdirs();
                    copyFileInto(status.getPath().toString(), localFile);
                    results.put(fullPath + status.getPath().getName(), localFile);
                }
            }
        } catch (IOException e) {
            log.error(e);
        }

        return results;
    }

    @Override
    public InputStream getInputStreamForFile(String fullPath, long startOffset, long maxReadLength) throws IOException {
        final String relativePath = mounter.getMountRelativePath(fullPath, mountPoint);
        final Path hdfsPath = new Path(hdfsBasePath, relativePath);
        final FSDataInputStream stream = fs.open(hdfsPath);
        try {
            stream.seek(startOffset);
        } catch (final IOException e) {
            Closeables2.closeQuietly(stream, log);
            throw new IOException("Failed to open " + fullPath + " with offset " + startOffset, e);
        }
        return stream;
    }


}
