package org.onedatashare.server.module.vfs;

import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.onedatashare.server.model.core.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class VfsResource extends Resource<VfsSession, VfsResource> {

  private FileObject fileObject;

  protected VfsResource(VfsSession session, String path, FileObject fileObject) {
    super(session, path);
    this.fileObject = fileObject;
  }

  public Mono<VfsResource> mkdir() {
    return initialize().doOnSuccess(vfsResource -> {
      try {
        fileObject.createFolder();
      } catch (FileSystemException e) {
        e.printStackTrace();
      }
    });
  }

  public Mono<VfsResource> delete() {
    return initialize().doOnSuccess(vfsResource -> {
      try {
        fileObject.delete();
      } catch (FileSystemException e) {
        e.printStackTrace();
      }
    });
  }

  @Override
  public Mono<VfsResource> select(String path) {
    return session.select(path);
  }

  public Mono<Stat> stat() {
    return initialize().map(VfsResource::onStat);
  }

  private Stat onStat() {
    Stat stat = new Stat();
    try {
      if(fileObject.isFolder()){
        stat.dir = true;
        stat.file = false;
      }
      else {
        stat = fileContentToStat(fileObject);
      }
      stat.name = fileObject.getName().getBaseName();

      if(stat.dir) {
        FileObject[] children = fileObject.getChildren();
        ArrayList<Stat> files = new ArrayList<>();
        for(FileObject file : children) {
          files.add(fileContentToStat(file));
        }
        stat.setFiles(files);
      }
    } catch (FileSystemException e) {
      e.printStackTrace();
    }

    return stat;
  }

  public Stat fileContentToStat(FileObject file) {
    Stat stat = new Stat();
    FileContent fileContent = null;
    try {
      fileContent = file.getContent();
      if(file.isFolder()) {
        stat.dir = true;
        stat.file = false;
      }
      else {
        stat.file = true;
        stat.dir = false;
        stat.size = fileContent.getSize();
      }
      stat.name = file.getName().getBaseName();
      stat.time = fileContent.getLastModifiedTime() / 1000;
    } catch (FileSystemException e) {
      e.printStackTrace();
    }
    return stat;
  }

  public VfsTap tap() {

    VfsTap vfsTap = new VfsTap();
    vfsTap.tapStat();
    return vfsTap;
  }

  public VfsDrain sink() {
    return new VfsDrain().start();
  }

  public VfsDrain sink(Stat stat) {
    return new VfsDrain().start(path + stat.getName());
  }

  public Mono<Stat>transferStat() {
    return initialize()
            .map(VfsResource::onStat)
            .map( s -> {
              List<Stat> sub = new LinkedList<>();
              long directorySize = 0L;

              if(s.isDir()){
                try {
                  directorySize = buildDirectoryTree(sub, fileObject.getChildren(), "/");
                }
                catch(FileSystemException fse){
                  System.out.println("Exception encountered while generating file objects within a folder");
                  fse.printStackTrace();
                }
              }
              else{
                fileResource = true;
                sub.add(s);
                directorySize = s.getSize();
              }
              s.setFilesList(sub);
              s.setSize(directorySize);
              return s;
            });
  }

  public Long buildDirectoryTree(List<Stat> sub, FileObject[] fileObjects, String relativeDirName){
    long directorySize = 0L;

    for(FileObject fileObject : fileObjects){
      try {
        if (fileObject.isFile()) {
          Stat fileStat = fileContentToStat(fileObject);
          fileStat.setName(relativeDirName + fileStat.getName());
          directorySize += fileStat.getSize();
          sub.add(fileStat);
        } else {
          directorySize += buildDirectoryTree(sub, fileObject.getChildren(), relativeDirName + fileObject.getName() + "/");
        }
      }
      catch (FileSystemException e) {
        e.printStackTrace();

      }
    }
    return directorySize;
  }

  class VfsTap implements Tap {
    FileContent fileContent;
    Stat transferStat;
    long size;

    @Override
    public Stat getTransferStat() {
      return transferStat;
    }

    @Override
    public Flux<Slice> tap(Stat stat, long sliceSize) {
      String downloadPath = path;
      if(!fileResource)
        downloadPath += stat.getName();
      try {
        fileObject = session.fileSystemManager.resolveFile(downloadPath , session.fileSystemOptions);
        fileContent = fileObject.getContent();
      }
      catch (FileSystemException e) {
        e.printStackTrace();
      }
      size = stat.getSize();
      return tap(sliceSize);
    }

    public void tapStat(){
      transferStat = transferStat().block();
    }

    public Flux<Slice> tap(long sliceSize) {
      int sliceSizeInt = Math.toIntExact(sliceSize);
      int sizeInt = Math.toIntExact(size);
      InputStream inputStream = null;
      try {
        inputStream = fileContent.getInputStream();
      } catch (FileSystemException e) {
        e.printStackTrace();
      }
      InputStream finalInputStream = inputStream;
      return Flux.generate(
              () -> 0,
              (state, sink) -> {
                if (state + sliceSizeInt < sizeInt) {
                  byte[] b = new byte[sliceSizeInt];
                  try {
                    finalInputStream.read(b, state, sliceSizeInt);
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
                  sink.next(new Slice(b));
                } else {
                  int remaining = sizeInt - state;
                  byte[] b = new byte[remaining];
                  try {
                    finalInputStream.read(b, state, remaining);
                    finalInputStream.close();
                  } catch (IOException e) {
                    e.printStackTrace();
                  }
                  sink.next(new Slice(b));
                  sink.complete();
                }
                return state + sliceSizeInt;
              });
    }


  }

  class VfsDrain implements Drain {
    OutputStream outputStream;
    long uploaded = 0L;

    @Override
    public VfsDrain start() {
      try {
        fileObject.createFile();
        outputStream = fileObject.getContent().getOutputStream();
      } catch (FileSystemException e) {
        e.printStackTrace();
      }
      return this;
    }

    @Override
    public VfsDrain start(String drainPath) {
      try {
        fileObject = session.fileSystemManager.resolveFile(drainPath, session.fileSystemOptions);
        return start();
      }
      catch(FileSystemException fse){
        System.out.println("Exception encountered while creating file object");
        fse.printStackTrace();
      }
      return null;
    }

    @Override
    public void drain(Slice slice) {
      try {
        outputStream.write(slice.asBytes());
      } catch (IOException e) {
        e.printStackTrace();
      }
      uploaded += slice.length();
    }

    @Override
    public void finish() {
      try {
        outputStream.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

  }
}

@@ -4123,32 +4123,183 @@
        %7D else %7B%0A
+          String dirName = fileObject.getName().toString();%0A          dirName = relativeDirName + dirName.substring(dirName.lastIndexOf(%22/%22)+1) + %22/%22;%0A
           direct
@@ -4363,52 +4363,15 @@
 (), 
-relativeDirName + fileObject.getName() + %22/%22
+dirName
 );%0A 

Fix relative dirname issue in FTP outward transfer