package org.jgroups.tests;

import org.jgroups.blocks.GridFile;
import org.jgroups.blocks.GridFilesystem;
import org.jgroups.blocks.ReplCache;
import org.jgroups.util.Util;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Bela Ban
 * todo: regexp for file ls, rm, down and up
 * todo: recursive up and down
 */
public class GridFilesystemTest {
    static final Map<String,Command> commands=new HashMap<>();
    static String current_dir="/";
    static final String HOME=System.getProperty("user.home");

    static {
        commands.put("mkdir", new mkdir());
        commands.put("ls",    new ls());
        commands.put("cd",    new cd());
        commands.put("pwd",   new pwd());
        commands.put("rm",    new rm());
        commands.put("up",    new up());
        commands.put("down",  new down());
    }

    public static void main(String[] args) throws Exception {
        String props="udp.xml";
        String cluster_name="imfs-cluster";
        String metadata_cluster_name="metadata-cluster";
        short default_repl_count=1;
        int default_chunk_size=4000;
        GridFilesystem fs;

        for(int i=0; i < args.length; i++) {
            if(args[i].equals("-props")) {
                props=args[++i];
                continue;
            }
            if(args[i].equals("-cluster_name")) {
                cluster_name=args[++i];
                continue;
            }
            if(args[i].equals("-metadata_cluster_name")) {
                metadata_cluster_name=args[++i];
                continue;
            }
            if(args[i].equals("-repl_count")) {
                default_repl_count=Short.parseShort(args[++i]);
                continue;
            }
            if(args[i].equals("-chunk_size")) {
                default_chunk_size=Integer.parseInt(args[++i]);
                continue;
            }

            System.out.println("GridFilesystemTest [-props <JGroups config>] [-cluster_name <name>] " +
                    "[-metadata_cluster_name <name>] [-repl_count <count>] [-chunk_size <size (bytes)>]");
            return;
        }

        ReplCache<String,byte[]> data=new ReplCache<>(props, cluster_name);
        ReplCache<String, GridFile.Metadata> metadata=new ReplCache<>(props, metadata_cluster_name);
        data.start();
        metadata.start();

        fs=new GridFilesystem(data, metadata, default_repl_count, default_chunk_size);
        loop(fs);

        data.stop();
        metadata.stop();
    }

    private static void loop(GridFilesystem fs) {
        while(true) {
            try {
                System.out.print("> ");
                String line=Util.readLine(System.in);
                if(!execute(fs, line))
                    break;
            }
            catch(Throwable t) {
                t.printStackTrace();
            }
        }
    }

    private static boolean execute(GridFilesystem fs, String line) {
        String[] comps=parseCommandLine(line);
        if(comps == null || comps.length == 0) {
            help();
            return true;
        }
        if(comps[0].equalsIgnoreCase("quit") || comps[0].equalsIgnoreCase("exit"))
            return false;
        if(comps[0].equalsIgnoreCase("help")) {
            help();
            return true;
        }
        Command cmd=commands.get(comps[0]);
        if(cmd == null) {
            System.err.println(comps[0] + " not known");
            help();
            return true;
        }
        String[] args=null;
        if(comps.length > 1) {
            args=new String[comps.length -1];
            System.arraycopy(comps, 1, args, 0, args.length);
        }
        cmd.execute(fs, args);
        return true;
    }

    private static void help() {
        StringBuilder sb=new StringBuilder("Valid commands:\nhelp\nquit\nexit\n");
        for(Command cmd: commands.values()) {
            String help=cmd.help();
            if(help != null)
                sb.append(help).append("\n");
        }
        System.out.println(sb.toString());
    }

    private static String[] parseCommandLine(String line) {
        if(line == null)
            return null;
        return line.trim().split(" ");
    }


    private static interface Command {
        void execute(GridFilesystem fs, String[] args);
        String help();
    }

    private static class ls implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String options=parseOptions(args);
            boolean recursive=options.contains("R");
            boolean detailed=options.contains("l");
            String[] files=getNonOptions(args);
            if(files == null || files.length == 0)
                files=new String[]{current_dir};
            for(String str: files) {
                if(!str.startsWith(File.separator))
                    str=current_dir + File.separator + str;
                File file=fs.getFile(str);
                if(!file.exists()) {
                    System.err.println("File " + file + " doesn't exist");
                    continue;
                }
                print(file, detailed, recursive, 0, file.isDirectory());
            }
        }

        public String help() {
            return "ls [-lR] [dirs | files]";
        }
    }

    private static class mkdir implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String options=parseOptions(args);
            boolean recursive=options.contains("p");
            String[] dir_names=getNonOptions(args);
            if(dir_names == null || dir_names.length < 1) {
                System.err.println(help());
                return;
            }
            for(String dir: dir_names) {
                if(!dir.startsWith(File.separator))
                    dir=current_dir + File.separator + dir;
                File file=fs.getFile(dir);
                boolean result;
                if(recursive)
                    result=file.mkdirs();
                else
                    result=file.mkdir();
                if(!result)
                    System.err.println("failed creating " + dir);
            }
        }

        public String help() {
            return "mkdir [-p] dirs";
        }
    }


    private static class rm implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String options=parseOptions(args);
            boolean recursive=options.contains("r");
            String[] dir_names=getNonOptions(args);
            if(dir_names == null || dir_names.length < 1) {
                System.err.println(help());
                return;
            }
            for(String dir: dir_names) {
                if(!dir.startsWith(File.separator))
                    dir=current_dir.equals(File.separator)? current_dir + dir : current_dir + File.separator + dir;
                File file=fs.getFile(dir);
                if(!file.exists()) {
                    System.err.println(file.getName() + " doesn't exist");
                    return;
                }
                if(file.isFile()) {
                    if(!file.delete())
                        System.err.println("cannot remove " + file.getName());
                    return;
                }

                if(!recursive) {
                    if(!file.delete())
                        System.err.println("cannot remove " + file.getName() + ": is a directory");
                }
                else {
                    if(!delete(file)) { // recursive delete
                        System.err.println("recursive removal of " + file.getName() + " failed");
                    }
                }
            }
        }

        public String help() {
            return "rm [-fr] <files or dirs>";
        }
    }


    private static class cd implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String[] tmp=getNonOptions(args);
            String target_dir="~";
            if(tmp != null && tmp.length == 1)
                target_dir=tmp[0];

            if(target_dir.equals(".."))
                target_dir=new File(current_dir).getParent();
            if(target_dir.contains("~") && HOME != null)
                target_dir=target_dir.replace("~", HOME);
            if(!target_dir.trim().startsWith(File.separator)) {
                target_dir=current_dir.equals(File.separator)?
                        current_dir + target_dir :
                        current_dir + File.separator + target_dir;
            }
            File dir=fs.getFile(target_dir);
            if(!dir.exists()) {
                System.err.println("Directory " + target_dir + " doesn't exist");
            }
            else
                current_dir=target_dir;
        }

        public String help() {
            return "cd [dir]";
        }
    }

    private static class pwd implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            System.out.println(current_dir);
        }

        public String help() {
            return "pwd";
        }
    }


    private static class up implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String   options=parseOptions(args);
            String[] real_args=getNonOptions(args);
            boolean  overwrite=options.contains("f");

            if(real_args.length == 0) {
                System.err.println(help());
                return;
            }

            String local_path=real_args[0], grid_path=real_args.length > 1? real_args[1] : null;
            if(HOME != null && local_path.contains("~"))
                local_path=local_path.replace("~", HOME);
            String target_path=grid_path != null? grid_path : local_path;
            if(target_path.contains("~"))
                target_path=target_path.replace("~", HOME);
            if(target_path.equals("."))
                target_path=current_dir;

            File target=fs.getFile(target_path);
            if(!overwrite && target.exists() && target.isFile()) {
                System.err.println("grid file " + target_path + " already exists; use -f to force overwrite");
                return;
            }
            
            if(target.exists() && target.isDirectory()) {
                String[] comps=Util.components(local_path, File.separator);
                String filename=comps[comps.length - 1];
                target_path=target_path + (target_path.equals(File.separator)? filename : File.separator + filename);
            }

            FileInputStream in=null;
            OutputStream out=null;
            try {
                in=new FileInputStream(local_path);
                File out_file=fs.getFile(target_path);
                if(out_file.exists() && out_file.isDirectory()) {
                    System.err.println("target " + target_path + " is a directory");
                    return;
                }
                if(out_file.exists() && out_file.isFile() && overwrite) {
                    if(out_file instanceof GridFile)
                        ((GridFile)out_file).delete(true);
                    else
                        out_file.delete();
                }

                out=fs.getOutput((GridFile)out_file);
                byte[] buf=new byte[50000];
                int len, total=0;
                while((len=in.read(buf, 0, buf.length)) != -1) {
                    out.write(buf, 0, len);
                    total+=len;
                }
                System.out.println("uploaded " + local_path + " to " + target_path + " (" + total + " bytes)");
            }
            catch(FileNotFoundException e) {
                System.err.println("local file " + local_path + " not found");
            }
            catch(IOException e) {
                System.err.println("cannot create " + target_path);
            }
            finally {
                Util.close(in);
                Util.close(out);
            }
        }

        public String help() {
            return "up [-f] <local path> [<grid path>]";
        }
    }


    private static class down implements Command {

        public void execute(GridFilesystem fs, String[] args) {
            String   options=parseOptions(args);
            String[] real_args=getNonOptions(args);
            boolean  overwrite=options.contains("f");

            if(real_args.length == 0) {
                System.err.println(help());
                return;
            }

            String grid_path=real_args[0], local_path=real_args.length > 1? real_args[1] : null;
            if(HOME != null && grid_path.contains("~"))
                grid_path=grid_path.replace("~", HOME);
            String target_path=local_path != null? local_path : grid_path;
            if(target_path.contains("~"))
                target_path=target_path.replace("~", HOME);
            if(target_path.equals("."))
                target_path=System.getProperty("user.dir");

            File target=new File(target_path);
            if(!overwrite && target.exists() && target.isFile()) {
                System.err.println("grid file " + target_path + " already exists; use -f to force overwrite");
                return;
            }

            if(target.exists() && target.isDirectory()) {
                String[] comps=Util.components(grid_path, File.separator);
                String filename=comps[comps.length - 1];
                target_path=target_path + (target_path.endsWith(File.separator)? filename : File.separator + filename);
            }

            InputStream in=null;
            FileOutputStream out=null;
            try {
                in=fs.getInput(grid_path);
                File out_file=new File(target_path);
                if(out_file.exists() && out_file.isDirectory()) {
                    System.err.println("target " + target_path + " is a directory");
                    return;
                }
                out=new FileOutputStream(out_file);
                byte[] buf=new byte[50000];
                int len, total=0;
                while((len=in.read(buf, 0, buf.length)) != -1) {
                    out.write(buf, 0, len);
                    total+=len;
                }
                System.out.println("downloaded " + local_path + " to " + target_path + " (" + total + " bytes)");
            }
            catch(FileNotFoundException e) {
                System.err.println("local file " + local_path + " not found");
            }
            catch(IOException e) {
                System.err.println("cannot create " + target_path);
            }
            finally {
                Util.close(in);
                Util.close(out);
            }
        }

        public String help() {
            return "down [-f] <grid path> [<local path>]";
        }
    }


    private static String parseOptions(String[] args) {
        StringBuilder sb=new StringBuilder();
        if(args == null)
            return "";
        for(String str: args) {
            if(str.startsWith("-"))
                sb.append(str.substring(1));
        }
        return sb.toString();
    }

    private static String[] getNonOptions(String[] args) {
        if(args == null)
            return null;
        int cnt=0;
        for(String str: args)
            if(!str.startsWith("-"))
                cnt++;
        String[] retval=new String[cnt];
        cnt=0;
        for(String str: args)
            if(!str.startsWith("-"))
                retval[cnt++]=str;
        return retval;
    }

    private static boolean delete(File dir) {
        boolean retval=true;
        if(dir == null)
            return false;
        File[] files=dir.listFiles();
        if(files != null) {
            for(File file: files) {
                if(!delete(file))
                    retval=false;
            }
        }
        boolean rc=dir instanceof GridFile? ((GridFile)dir).delete(true) : dir.delete();
        if(!rc)
            retval=false;
        return retval;
    }

    private static void print(File file, boolean details, boolean recursive, int indent, boolean exclude_self) {
        if(file.isDirectory()) {
            if(!exclude_self)
                System.out.print(print(file, details, indent));
            File[] children=file.listFiles();
            for(File child: children) {
                if(!recursive)
                    System.out.print(print(child, details, indent));
                else {
                    print(child, details, recursive, indent +4, false);
                }
            }
            if(children.length > 0)
                System.out.println("");
        }
        else {
            String tmp=print(file, details, indent);
            System.out.print(tmp);
        }
    }

    private static String print(File file, boolean details, int indent) {
        StringBuilder sb=new StringBuilder();
        if(file.isDirectory()) {
            if(details)
                sb.append(indent(indent));
            sb.append(file.getName()).append("/");
        }
        else {
            if(details)
                sb.append(indent(indent));
            sb.append(file.getName());
            if(details) {
                sb.append(" " + Util.printBytes(file.length()));
                if(file instanceof GridFile)
                    sb.append(", chunk_sise=" + ((GridFile)file).getChunkSize());
            }

        }
        sb.append(details? '\n' : ' ');
        return sb.toString();
    }

    private static String indent(int num) {
        StringBuilder sb=new StringBuilder();
        for(int i=0; i < num; i++)
            sb.append(' ');
        return sb.toString();
    }


}