package nxm.sys.lib;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.Duration;

import org.apache.commons.net.ftp.FTPClient;

import nxm.sys.inc.Debug;
import nxm.sys.inc.InternalUseOnly;

/** ****** COMMONS-NET dependent version **********************
    Resource for interacting with a FTP Server.

    @author Tom Schneider, Jeff Schoen
 */
public class FtpResource extends SocketResource {

  /** Socket action/state flags */
  private FTPClient ftp;
  private String outName;
  // userName, userPass and directory are qualifiers that may not exist in every call;
  // therefore, make them static so that they are consistent with what the user specified
  private static String userName = "";
  private static String userPass = "";
  private static String ftpDir = "";

  // Is the FTP client using passive mode?
  private static boolean passive = false;

  // Is this the empty version of FtpResource?
  private static final boolean isEmptyVersion = false;

  /** default constructor */
  public FtpResource () {}

  public void setURL (String str) {
    urlstr = str;
    // peel off various header formats
    if (str.startsWith("ftp://")) str = str.substring(6);
    else if (str.startsWith("ftp:/")) str = str.substring(5);
    else if (str.startsWith("ftp:")) str = str.substring(4);

    // first chop off the file name
    int i = str.indexOf("/");
    if(i<0) throw new MidasException("Bad file name. Syntax: ftp:<ip address>:<port>/<filename> or ftp:<ip address>/<filename>");
    outName = str.substring(i+1);
    str = str.substring(0,i);

    // check for port
    i = str.indexOf(":");
    if (i>0) {
      port = Convert.s2l(str.substring(i+1));
      host = str.substring(0,i);
    }
    else {
      port = -1;
      host = str;
    }
  }

  public boolean open () {
    isOpen = false;

    double dataTOut = 0.0;
    double soTOut = 0.0;
    if (qualTable != null){
      userName = qualTable.getS("USER",userName);
      userPass = qualTable.getS("PASS",userPass);
      ftpDir   = qualTable.getS("FTPDIR",ftpDir);
      dataTOut = qualTable.getD("TIMEOUT",dataTOut);
      soTOut   = qualTable.getD("SOTIMEOUT",soTOut);
    }
    // Set socket timeout in milliseconds (for logging in) - added in NeXtMidas 4.1.3
    int soTOutMs = (int)(soTOut * 1000);
    if ((soTOut > 0) && (soTOutMs <= 0)) soTOutMs = 1; // make sure it doesn't round down to 0

    // Set data timeout in milliseconds (for reading/writing data)
    timeout = (int)(dataTOut * 1000);
    if ((dataTOut > 0) && (timeout <= 0)) timeout = 1; // make sure it doesn't round down to 0

    try {
      ftp = new FTPClient();
      if(port>0) ftp.connect(host, port);
      else       ftp.connect(host);

      ftp.setSoTimeout(soTOutMs);

      if (passive) ftp.enterLocalPassiveMode();
      ftp.login(userName, userPass);
      ftp.setDataTimeout(Duration.ofMillis(timeout));
      ftp.setFileType(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);

      // change the directory if the user has specified it
      if(!ftpDir.equals("")) ftp.cwd(ftpDir);

      if (dir > DIR_INOUT) { // output only
        sos = getOutputStream(false);
        System.out.printf("Putting file: %s on ftp:%s\n", outName, host);
      } else if (dir <= DIR_INOUT) {
        sis = getInputStream(false);
        System.out.printf("getting file: %s on ftp:%s\n", outName, host);
      }
      isOpen = true;
    }
    catch (Exception e) {
      if (M.debug(Debug.TRACE)) M.warning("FtpResource - exception during open()" + e);
    }
    return isOpen;
  }

  public boolean close () {
    try {
      if (sis    != null) { sis.close();    sis = null;    }
      if (sos    != null) { sos.close();    sos = null;    }
      if (ftp    != null) { ftp.logout(); ftp.disconnect(); ftp = null; }
      isOpen = false;
    }
    catch (IOException e) {
      isOpen = true;
    }
    return isOpen;
  }

  /** Internal call to open input stream. */
  protected InputStream getInputStream (boolean indefinite)  {
    while (isOpen && (sis == null)) {
      try {
        sis = ftp.retrieveFileStream(outName);
      }
      catch (Exception e) {
        throw new MidasException("Error getting file! " + e);
      }
    }
    return sis;
  }

  /** Internal call to open output stream. */
  protected OutputStream getOutputStream (boolean indefinite) {
    while (isOpen && (sos == null)) {
      try {
        sos = ftp.storeFileStream(outName);
      }
      catch(Exception e){
        throw new MidasException("Error putting file! " + e);
      }
    }
    return sos;
  }

  /** Is this the empty version of FtpResource?
   *
   *  @return true if using the empty version of FtpResource, false otherwise
   *  @since NeXtMidas 4.1.4
   */
  @InternalUseOnly
  public static boolean getIsEmptyVersion () {
    return isEmptyVersion;
  }

  /** Sets whether or not to use passive mode with the FTP client.
   *
   *  @param passive Whether or not to use passive mode with the FTP client.
   *  @since NeXtMidas 4.2.1
   */
  @InternalUseOnly
  public static void setPassive (boolean passive) {
    FtpResource.passive = passive;
  }

  /** Is the FTP client using passive mode?
   *
   * @return Whether the FTP client is using passive mode
   * @since NeXtMidas 4.2.1
   */
  public static boolean getPassive () {
    return passive;
  }
}
