package nxm.sys.net;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.zip.ZipEntry;

import nxm.sys.inc.PacketHandler;
import nxm.sys.inc.PlotFile;
import nxm.sys.lib.BaseFile;
import nxm.sys.lib.Data;
import nxm.sys.lib.DataFile;
import nxm.sys.lib.FileName;
import nxm.sys.lib.HttpResource;
import nxm.sys.lib.IOResource;
import nxm.sys.lib.MFormat;
import nxm.sys.lib.Midas;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;
import nxm.sys.lib.TarFile;
import nxm.sys.lib.TarFile.TarEntry;
import nxm.sys.lib.Time;

/** A class to display a list of files and their contents in an Browser.

  @author Jeff Schoen
*/
public class HFiles extends HSource {

  private String dirpath;
  private Midas M;
  private Parser auxes;
  private Date   startDate;             // Date this instance was started (created)
  private static boolean debug = false; // enable to turn on debug messages

  /** Creates a new instances that serves files in a given directory. <br>
      <br>
      Example:
      <pre>
        Creates a link to "/home/smith/nxm210/nxm/sys/dat/" that is
        accessible via "http://localhost/dat/":

          new HFiles("/home/smith/nxm210/nxm/sys/dat/","dat")
      </pre>

      @param name    The name of the directory (as used via http).
      @param dirpath The directory path (as exists on disk).
   */
  public HFiles (String name, String dirpath) {
    if (!name.endsWith("/")) name += '/';
    if (!dirpath.endsWith("/")) dirpath += '/';
    this.name = name;
    this.dirpath = dirpath;
    this.startDate = new Date();
    if (debug) Shell.writeln("HFiles() name="+name+" dirpath="+dirpath+" startDate="+startDate);
  }

  /** Serves up all files in/under a given AUX or list of AUXs.
      @param name    Name of the serve (usually "Files").
      @param M       Midas context to use.
      @param auxList AUX list to serve up. One or more AUX names separated by a "|"
                     or "*" to serve up everything in AUX.READ. (For backwards-compatibility,
                     passing null or "" is the same as "*".)
   */
  public HFiles (String name, Midas M, String auxList) {
    if (!name.endsWith("/")) name += '/';
    this.name = name;
    this.M = M;
    this.startDate = new Date();
    if (auxList==null || auxList.length()==0 || auxList.equals("*"))
      auxList = M.results.getString("AUX.READ");
    auxes = new Parser(auxList,'|');
  }

  /** Set if debugging is enabled
   *  @param val true to enable debugging
   *  @since NeXtMidas 2.7.1 to turn on debugging */
  public static void setDebug(boolean val) { debug = val;  }
  /** Is debugging enabled
   *  @return true if debugging is enabled
   *  @since NeXtMidas 2.7.1 to see if debugging is enabled */
  public static boolean isDebug()          { return debug; }

  public void handleRequest (String uri, HPage hp) {
    String name    = getName();
    String path    = uri.substring(name.length());
    File   listDir = null; // If not null, create index listing directory.
    if (debug) Shell.writeln("HFiles.handleRequest() "+hp.getMethod()+" uri="+uri+" path="+path);

    String args = null;
    int i = path.indexOf('?');
    if (i>0) {
      args = path.substring(i+1);
      path = path.substring(0,i);
    }

    if (dirpath!=null) {
      if (path.equals("") || path.endsWith("/")) path += "index.html";
      path = dirpath + path;
      File index = new File(path);
      if (!index.exists()) listDir = index.getParentFile();
    }
    else if (path.equals("")) {
      framesPage(hp); return;
    }
    else if (path.startsWith("auxesPage.html")) {
      auxesPage(hp); return;
    }
    else if (path.startsWith("filesPage.html")) {
      if (args==null) dummyPage(hp);
      else filesPage(hp,args); return;
    }
    else if (path.startsWith("fdataPage.html")) {
      if (args==null)  dummyPage(hp);
      else  fdataPage(hp,args); return;
    }
    else {
      i = path.indexOf('/');
      name = path.substring(i+1);
      if (i>0) {
        String aux = path.substring(0,i).toUpperCase();
        path = M.io.getAuxPath(aux) + name;
        if (aux.equals("*")) path = name;
      }
      else path = name;
      if (path.endsWith("/")) listDir = new File(path);
    }

    if (listDir != null) {
      String auxType = path.substring(0,path.indexOf(':')+1).toLowerCase();
      if (auxType.equals("jar:")||auxType.equals("tar:")||auxType.equals("zip:")) {
        containerIndexPage(hp, path, uri);  // since 3.3.0 - handle JAR/TAR/ZIP AUXs
      } else if (auxType.equals("http:")) {
        webIndexPage(hp,path,uri);  // since 3.3.0 - handle http aux
      }
      else indexPage(hp, listDir, uri);
    }
    else if (path.startsWith("ram:_")) {     // request for PIPED data file
      int ip = path.indexOf('.');            // For NeXtMidas 2.7.0 and prior clients
      if (ip>0) path = path.substring(0,ip); // strip off file extension for pipes in RAM AUX
      DataFile df = new DataFile (M,path);
      HFile.handlePipe (df, hp);
    }
    else {
      if (path.startsWith("ram:")) {
        int ip = path.indexOf('.');            // For NeXtMidas 2.7.0 and prior clients
        if (ip>0) path = path.substring(0,ip); // strip off file extension for files in RAM AUX
      }
      if (args!=null) execArgs(args);
      BaseFile bf = new BaseFile (M,path);
      HFile.handle (bf, hp, args);
    }
  }

  private void execArgs (String args) {
    if (server==null) return;
    Midas M = server.getMidas();
    if (M==null) return;
    int ie = args.indexOf('=');
    if (ie<0) return;
    String key = args.substring(0,ie);
    String value = args.substring(ie+1);
    int ip = key.indexOf('.');
    if (ip<0 && M.macro!=null && M.macro.controls.get(key)!=null) M.macro.controls.put(key+".ACTION",value);
    else M.results.put(key,value);
  }

  /** Handle indexing for special AUXs (JAR, ZIP, TAR)
      @since NeXtMidas 3.3.0
   */
  private void containerIndexPage (HPage hp, String path, String uri) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    // remove type: and subDir path
    String root = "";
    int rootStart = path.indexOf(':')+1;
    String prefix = path.substring(0,rootStart).toLowerCase();
    int rootEnd   = path.indexOf('!');
    if (rootEnd > 0) root = path.substring(rootStart,rootEnd);
    else             root = path.substring(rootStart);

    ArrayList<String> subEntries = new ArrayList<>();
    boolean isTar = prefix.equals("tar:");
    String subDir  = path.substring(path.indexOf('!')+1); // get just the sub-directory name
    if (subDir==null) subDir = "";  // root dir
    while (subDir.length()>0 && subDir.charAt(0)=='/') subDir = subDir.substring(1);  // remove any leading '/'
    BaseFile bf = isTar ? new TarFile(M,root) : new nxm.sys.lib.ZipFile(M,root);
    bf.open();
    Object[] entries = bf.getEntries();
    bf.close();
    if (entries != null) {
      final   Date modDate = new Date();
      final   SimpleDateFormat f = new SimpleDateFormat("dd-MMM-yyyy HH:mm");
      String  entryName = "";
      String  size = "0";
      MFormat sizeFormat = MFormat.getByteFormat();
      for (Object o : entries) {
        if (o instanceof ZipEntry) {
          ZipEntry z = (ZipEntry)o;
          entryName = scrubEntryName(z.getName(), subDir);
          if (entryName.equals("")) continue;
          size = sizeFormat.format((z.getSize()));          // get the size in a readable format
          modDate.setTime(z.getTime());
        }
        else if (o instanceof TarEntry) {
          TarEntry t = (TarEntry)o;
          entryName = scrubEntryName(t.getName(),subDir);
          if (entryName.equals("")) continue;
          size = sizeFormat.format((t.getSize()));          // get the size in a readable format
          modDate.setTime(t.getTime1970()*1000);
        }
        else continue;   // unknown entry type

        // add the html-formatted entry to the ArrayList
        subEntries.add("<a href='"+entryName+"'>"+entryName+"</a>"+
          StringUtil.padRight("",30-entryName.length()) + " " + f.format(modDate) +
          StringUtil.padLeft(size,4));
      }
    }
    // write the html
    writeIndexPage(hp, subEntries, uri);
  }

  private void webIndexPage (HPage hp, String path, String uri) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()

    ArrayList<String> subEntries = new ArrayList<>();
    String files[] = M.io.listFiles(path, null);
    HttpResource hr = (HttpResource) M.io.findResource(path, -1);

    if (files!=null && hr!=null) {
      String  size = "0";
      MFormat sizeFormat = MFormat.getByteFormat();
      final   Date modDate = new Date();
      final   SimpleDateFormat form = new SimpleDateFormat("dd-MMM-yyyy HH:mm");
      for (String entryName : files) {
        IOResource ior = M.io.findResource(path+entryName, -1);
        if (ior !=null) {
          ior.open();
          modDate.setTime(ior.lastModified());
          size = sizeFormat.format(ior.getLength());
          subEntries.add("<a href='"+entryName+"'>"+entryName+"</a>" +
              StringUtil.padRight("",30-entryName.length()) + " " + form.format(modDate) +
              StringUtil.padLeft(size,4));
          ior.close();
        }
      }
    }
    writeIndexPage(hp,subEntries,uri);
  }

  /** Method to pare down an entry name or return "" if the entry is blank or not in the right directory */
  private String scrubEntryName (String entryName, String subDir) {
    if (entryName.startsWith(subDir)) {
      entryName = entryName.substring(subDir.length());
      if (entryName.length()==0 || entryName.equals("")) return "";  // blank entry at the top, usually
      if (entryName.indexOf('/')!=-1 && entryName.indexOf('/')!=entryName.length()-1) return "";  // do not drill down
      return entryName;
    }
    else return "";  // not in the right sub-directory
  }

  private void writeIndexPage (HPage hp, ArrayList<String> data, String uri) {

    // if no entries, create the file not found page
    if (data.size()==0) {
      hp.openToBody("File Not Found");
      hp.writeln ("  <h1>File Not Found: " + uri + "</h1>");
      hp.closeFromBody();
      return;
    }

    Collections.sort(data);  // put the files in alpha order

    // if there are entries, create the html
    hp.openToBody("Index of "+uri);
    hp.writeln ("  <h1>Index of " + uri + "</h1>");
    hp.writeln ("  <pre>");
    for (int i = 0; i < data.size(); i++) {
      hp.writeln(data.get(i));
    }
    hp.writeln ("  </pre>");
    hp.closeFromBody();
  }

  /** Writes out a generic frames page to an {@link HPage}.
      @param hp The connection to write out to.
   */
  public void framesPage (HPage hp) {
    hp.setLastModified(startDate); // NTN: since this page is static, we can use startDate
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    hp.open();
    hp.writeln ("<html>");
    hp.writeln ("<head>");
    hp.writeln ("<title>HFiles Frame</title>");
    hp.writeln ("</head>");
    hp.writeln ("<frameset cols='25%,75%'>");
    hp.writeln ("<frameset rows='30%,70%'>");
    hp.writeln ("<frame src='auxesPage.html' name='auxesFrame'>");
    hp.writeln ("<frame src='filesPage.html' name='filesFrame'>");
    hp.writeln ("</frameset>");
    hp.writeln ("<frame src='fdataPage.html' name='fdataFrame'>");
    hp.writeln ("</frameset>");
    hp.writeln ("</html>");
    hp.close();
  }

  /** Writes out a generic AUX's page to an {@link HPage}.

      @param hp The connection to write out to.
   */
  public void auxesPage (HPage hp) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    hp.openToBody("HFiles Aux List");
    hp.writeln ("<div style=\"text-align:center;\"><h3>Midas Aux List</h3></div>");
    hp.writeln ("<table border='0' width='100%'>");
    for (auxes.reset(); auxes.more(); ) {
      String aux = auxes.next();
      String path = M.io.getAuxPath(aux);
      hp.writeln ("<tr><td nowrap><a href='filesPage.html?Aux"+aux+"' target='filesFrame'>"+aux+"</a></td>");
      hp.writeln ("<td nowrap>"+path+"</td></tr>");
    }
    hp.writeln ("</table>");
    hp.closeFromBody();
  }

  /** Writes out a generic list of files accessible via an AUX to an {@link HPage}.

      @param hp   The connection to write out to.
      @param args The arguments (must include the AUX).
   */
  public void filesPage (HPage hp, String args) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    String aux     = args.substring(3);
    String path    = M.io.getAuxPath(aux);
    String[] files = M.io.listFiles(path,"*","*");
    int numFiles   = (files != null) ? files.length : 0;
    hp.openToBody("HFiles Aux File List");
    hp.writeln ("<div style=\"text-align:center;\"><b>Aux = "+aux+"</b></div><br>");
    hp.writeln ("<table border='0' width='100%'><tr><td nowrap>");
    for (int i=0; i<numFiles; i++) {
      String fn = files[i];
      hp.writeln ("<a href='fdataPage.html?"+aux+"/"+fn+"&Text' target='fdataFrame'>"+fn+"</a><br>");
    }
    hp.writeln ("</td></tr></table>");
    hp.closeFromBody();
  }

  /** Writes out a generic index page to an {@link HPage}. This page lists the files in the given
      directory (similar to that produced by Apache when an index.html file does not exist for a
      given directory).

      @param hp   The connection to write out to.
      @param dir  The directory (on disk).
      @param name The directory name (URI path) (via HTTP connection).

      @since NeXtMidas 2.1.0
   */
  private void indexPage (HPage hp, File dir, String name) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    if (dir.isDirectory()) {
      File[] files = dir.listFiles();
      MFormat sizeFormat = MFormat.getByteFormat();
      Time    time       = new Time();

      hp.openToBody("Index of "+name);
      hp.writeln ("  <h1>Index of " + name + "</h1>");
      hp.writeln ("  <pre>");
      for (int i = 0; i < files.length; i++) {
        time.fromJ1970(files[i].lastModified() / 1000.0);

        String  fname     = files[i].getName();
        String  size      = sizeFormat.format(Long.valueOf(files[i].length()));
        String  date      = time.toString("VAX", 0);
        String  url       = name + fname;

        if (files[i].isDirectory()) {
          fname = fname + "/";
          url   = url   + "/";
        }

        if (!files[i].isHidden()) {
          hp.writeln("<a href='" + url + "'>" + fname + "</a>"
                   + StringUtil.padRight("",30-fname.length()) + " " + date + " "
                   + StringUtil.padLeft(size,4));
        }
      }
      hp.writeln ("  </pre>");
      hp.closeFromBody();
    } else {
      hp.openToBody("File Not Found");
      hp.writeln ("  <h1>File Not Found: " + name + "</h1>");
      hp.closeFromBody();
    }
  }

  /** Writes out a file-data page to an {@link HPage}
   *  @param hp   The connection to write out to
   *  @param args The arguments (must include the AUX)
   */
  public void fdataPage (HPage hp, String args) {
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    int i = args.lastIndexOf('/');
    int j = args.indexOf('&',i+1);
    String name = args.substring(i+1,j);
    String path = name, aux = "*";
    if (args.charAt(0)=='/') {
      aux = args.substring(0,i);
      path = args.substring(0,j);
    }
    else if (i>0) {
      aux = args.substring(0,i).toUpperCase();
      path = M.io.getAuxPath(aux) + name;
      if (aux.equals("*")) path = name;
    }
    String mode = args.substring(j+1);
    FileName fn = BaseFile.getFileNameFor(M,path);
    BaseFile bf = BaseFile.getInstanceFor(M,fn);
    DataFile df = null;
    bf.open();
    if (bf instanceof DataFile) df = (DataFile)bf;
    hp.openToBody("HFiles File Data Page");
//    if (mode.startsWith("T")) hp.writeln ("<body>");
//    else hp.writeln ("<body bgcolor=#D0D0D0>");
    hp.writeln ("Aux=<b>"+aux+"</b>&nbsp;&nbsp; File=<b>"+name+"</b>&nbsp;&nbsp; Display=");
    hp.writeln ("<a href='fdataPage.html?"+aux+"/"+name+"&Text'>Text</a>");
    hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&Tail'>Tail</a>");
    if (bf instanceof PlotFile) {
      hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&Auto'>Plot</a>");
    }
    if (df!=null && df.getType()/1000==1 && !Data.isString(df.getFormatType())) { /// 3.9.4 NXM-3570 should be okay - if string data not FFT related
      hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&FFT'>FFT</a>");
    }
    if (df!=null && df.getType()/1000==2) {
      hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&Line'>Line</a>");
      hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&Last'>LastFrame</a>");
    }
    if (df!=null && df.getType()/1000>2 ) {
      hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&Table'>Table</a>");
    }
    hp.writeln (" - <a href='"+aux+"/"+name+"'>DownLoad</a>");
    hp.writeln (" - <a href='fdataPage.html?"+aux+"/"+name+"&"+mode+"'>Refresh</a>");
    hp.writeln ("<br>");
    if (mode.equals("Load")) {
    }
    else if (mode.equals("Text") || mode.equals("Tail")) {
      String line; double start=0; PacketHandler pkh=null;
      int ncols=1, lines=50, tw=80, flags=0;
      hp.writeln ("<pre>");
      hp.writeln ( bf.listHeader() );
      if (df!=null) pkh = df.getPacketHandler();
      if (df!=null && df.typeClass>=2) tw = 160;
      if (df!=null && mode.equals("Tail")) start = Math.max(0.0,df.getSize()-20);
      ncols = Math.max(1,bf.listElementsPerLine(tw,null,flags));
      for (int k=0; k<lines; k++,start+=ncols) {
        line = bf.listElements (start,ncols,null,flags);
        if (line==null) break;
        if (pkh!=null && pkh.hasHeader()) hp.writeln( pkh.listHeader() );
        hp.write( line );
      }
      hp.writeln ("</pre>");
    }
    else if (mode.equals("Table")) {
      hp.writeln("<table style=\"margin-left:auto;margin-right:auto;\" border='2' cellpadding='2' cellspacing='0'>");
      for (double offset=0; offset<Math.min(100,df.size); offset++) {
        Table t = df.getDataTable(offset);
        Table.Iterator ti = t.iterator();
        if ((offset%20)==0) {
          hp.writeln("<tr>");
          for (i=0; i<ti.size; i++) hp.writeln("<td><b>"+ti.keys[i]+"</b></td>");
          hp.writeln("</tr>");
        }
        if (offset>=0) {
          hp.writeln("<tr>");
          for (i=0; i<ti.size; i++) hp.writeln("<td>"+ti.values[i]+"</td>");
          hp.writeln("</tr>");
        }
      }
      hp.writeln("</table>");
    }
    hp.closeFromBody();
    bf.close();
  }


  /** Writes out a blank (dummy) page to an {@link HPage}.

      @param hp The connection to write out to.
   */
  public void dummyPage (HPage hp) {
    hp.setLastModified(startDate); // NTN: since this page is static, we can use startDate
    if (hp.getMethod().equals("HEAD")) { return; } // don't return content for HEAD request, HPage will do open() and close()
    hp.openToBody("HFiles Dummy Page<");
    hp.closeFromBody();
  }

}
