package page.tools.management;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;

import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.handler.http.FormData;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.interfaces.*;
import org.wikiwebserver.handler.http.responder.FileResponder;

import page.config.SiteTemplatedPage;
import org.wikiwebserver.util.comparator.FileNameComparator;


public class FileTreeView implements HTTPResponder, CacheableHTTPResponse {
        
    public static final String LF = "\r\n";
    
    private boolean useJavaScript = true;
    private String resourcePath = "/templates/default/tree/";  
    private FormData formData;
        
    public String getHead() {
        String css = resourcePath + "tree.css";
        StringBuilder builder = new StringBuilder();
        builder.append("<link rel='stylesheet' href='" + css + "' type='text/css'>");
        if (this.useJavaScript) {
            String js = resourcePath + "tree.js";            
            builder.append("<script language='JavaScript' type='text/javascript' src='" + js + "'></script>");
        }
        return builder.toString();
    }
        
    public String getFileTree(String title, File root, File selected) {

        StringBuffer body = new StringBuffer();

        body.append("<div id='tree'><div class='nodeRoot'>");
        
        String[] titles = null;
        File[] files = new File[] { root };
        if (root != null)  titles = new String[] { title };
        
        body.append(fileTree(root, files, titles, selected)); 
        
        body.append("</div></div>");
            
        return body.toString();
    }   
    
    public void init(HTTPHandler conn) throws IOException { 
        formData = conn.getRequest().getFormData();
        conn.getResponse().getHeaders().set("Content-Type", "text/html");
    }
    
    public Object respond(HTTPHandler conn) throws IOException { 
        
        SiteTemplatedPage page = new SiteTemplatedPage();
        page.init(conn);
        String userAgent = conn.getRequest().getHeaders().getFirst("User-Agent");
        this.useJavaScript = !WareHouse.isMobileUserAgent(userAgent);      
        
        page.setTitle("Files - WikiWebServer.org");
        page.appendToHead(getHead());
        page.append("<h1>Files</h1>");
        page.append("<p>Navigate files on WikiWebServer.</p>");
        
        File selFile = new File("."); 
              
        if (formData != null) {
            // Read posted data
            
            String selected = formData.getFirst("selected");
            if (selected != null) {
                selFile = FileResponder.getFileFromURL(selected);  
            }
            
            String path = formData.getFirst("path");
            if (path != null) {
                File dirFile = FileResponder.getFileFromURL(path);    
                
                String id = constructID(dirFile);
                if (isDirectory(dirFile)) {
                    String mode = formData.getFirst("mode");
                    if (mode == null) {
                        return id + "\r\n" + fileTree(dirFile, null);
                    } else if (mode.equals("gallery")) {
                        return id + "\r\n" + imageTree(dirFile, null);
                    } else if (mode.equals("detail")) {
                        return id + "\r\n" + fileDetailTree(dirFile, null);                    
                    } else {
                        return id + "\r\n" + fileTree(dirFile, null);
                    }
                } else {
                    return id + "\r\n" + errorResponse("Invalid path.");
                }
            }
        }

        page.append(getFileTree("WikiWebServer", new File("."), selFile));
        
        return page;                       
    }
    
    private String getDirectoryAnchor(String id, String href) {
        if (useJavaScript) {
            String js = "javascript:" + constructJavaScriptCall("showPath", new String[] { id, href });
            return "<a href='javascript:void(0)' onclick='" + js + "'>";
        } else {
            return "<a href='?selected=" + href + "'>";
        }
    }
        
    private String getDirectoryElement(String title, File file, File selected) {
        StringBuffer buffer = new StringBuffer();
        String href = WareHouse.getUrlPathForFile(file);
        String id = constructID(file);
        boolean open = isFileContainedIn(selected, file);
        buffer.append("<div class='nodeParent'>");
        buffer.append(getDirectoryAnchor(id, href));
        String folderIcon = open ? "folder-open.png" : "folder.png";
        buffer.append("<img src='" + resourcePath + "" + folderIcon + "' width='16' height='16' alt='node' id='" + id + "_icon' class='fileIcon'/></a> ");
        buffer.append(getDirectoryAnchor(id, href));
        buffer.append(title +"</a>");
        buffer.append("</div><div id='" + id + "' class='subPath'>");
        if (open) buffer.append("<div>" + fileTree(file, selected) + "</div>");
        buffer.append("</div>");
        return buffer.toString();
    }

    public static String getFileElement(File file, String iconRoot, boolean sel) {
        
        StringBuffer buffer = new StringBuffer();
        
        String name = file.getName();
        String href = WareHouse.getUrlPathForFile(file);
        boolean editable = isEditable(file);
        
        String icon = WareHouse.getIconForFile(file, iconRoot);
        String openTip = "Open " + name;
        String editTip = editable ? "Edit " + name : "Binary file";
        String editPath = WareHouse.SOURCE_EDITOR_URL + "?path=" + href;
        buffer.append("<div class='fileNode'>");
        
        buffer.append("<a title='" + openTip + "' href='" + href + "'>");
        buffer.append("<img border='0' src='" +  icon + "' width='16' height='16' alt='" + openTip + "' class='fileIcon'/>");
        buffer.append("</a>");
        
        buffer.append(" <a title='" + openTip + "' href='" + href + "'>");
        if (sel) buffer.append("<span class='selected'>");
        buffer.append(name.substring(0, Math.min(name.length(), 30)));
        if (sel) buffer.append("</span>");
        buffer.append("</a>");
        
        if (editable) {
            buffer.append(" [<a title='" + editTip + "' href='" + editPath + "'>");
            buffer.append("Edit</a>]"); 
        }
        
        buffer.append("</div>");
        return buffer.toString();
    }
    
    private static String getFileDetailElement(File file, String iconRoot) {
        
        StringBuffer buffer = new StringBuffer();
        boolean editable = isEditable(file);
        String name = file.getName();
        String href = WareHouse.getUrlPathForFile(file);
        String icon = WareHouse.getIconForFile(file, iconRoot);
        String openTip = "Open " + href;
        String editTip = editable ? "Edit " + name : "Binary file";
        String editPath = WareHouse.SOURCE_EDITOR_URL + "?path=" + href;
        buffer.append("<div class='fileNodeDetail'>");
        
        buffer.append("<div class='fileDateDetail'>");
        buffer.append(WareHouse.formatStandardDate(file.lastModified()));
        buffer.append("</div>");        
        
        buffer.append("<div class='fileSizeDetail'>");
        buffer.append(formatNumber(file.length()));
        buffer.append("</div>");        
        
        buffer.append("<a title='" + openTip + "' href='" + href + "'>");
        buffer.append("<img border='0' src='" +  icon + "' width='16' height='16' alt='" + openTip + "' class='fileIcon'/>");
        buffer.append("</a>");
        
        buffer.append(" <a title='" + openTip + "' href='" + href + "'>");
        buffer.append(name.substring(0, Math.min(name.length(), 60)));
        buffer.append("</a>");
        
        if (editable) {
            buffer.append(" [<a title='" + editTip + "' href='" + editPath + "'>");
            buffer.append("Edit</a>]"); 
        }
        
        buffer.append("</div>");
        return buffer.toString();
    }    
    
    private String getThumbnailElement(File file) {
        
        String name = file.getName();
        String busyImage = "/templates/default/tree/busy.gif";
        String id = constructID(file);
        String ele = "<a id='" + id + "_link' href='#'>"
                   + "<img id='" + id + "' src='" + busyImage + "' alt='" + name + "'></a>";
        return ele;
    }
    
    private String fileTree(File parent, File selected) {
        File[] files = listFiles(parent);
        StringBuilder builder = new StringBuilder();
        builder.append(fileTree(parent, files, null, selected));
        if (selected == null) {
            builder.append("\r\njavascript:" + updateIcon(constructID(parent)));
        }
        return builder.toString();
    }
        
    private String fileTree(File parent, File[] files, String[] titles, File selected) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(listDirectories(files, titles, selected));
        int count = numFilesVisible(files);
        if (count > 0) {
            buffer.append("<div class='viewHeading'>");
            if (this.useJavaScript) {
                buffer.append(optionLinks(files, parent)); 
            }
            buffer.append("Listing files in " + parent);
            buffer.append("</div>");
            buffer.append(listFiles(files, selected));
            buffer.append("<div class='viewFooter'>");
            String bytes = formatNumber(numBytesVisible(files));
            buffer.append(bytes + " bytes in " + count + " files.");
            buffer.append("</div>");
        }
        return buffer.toString();
    }
    
    private String fileDetailTree(File parent, File selected) {
        File[] files = listFiles(parent);
        StringBuilder builder = new StringBuilder();
        builder.append(fileDetailTree(parent, files, null, selected));
        if (selected == null) {
            builder.append("\r\njavascript:" + updateIcon(constructID(parent)));
        }
        return builder.toString();
    }
        
    private String fileDetailTree(File parent, File[] files, String[] titles, File selected) {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append(listDirectories(files, titles, selected));
        int count = numFilesVisible(files);
        if (count > 0) {
            buffer.append("<div class='viewHeading'>");
            buffer.append(optionLinks(files, parent));            
            buffer.append("Listing files in " + parent);
            buffer.append("</div>");
            buffer.append(listFilesDetail(files, parent));
            buffer.append("<div class='viewFooter'>");
            String bytes = formatNumber(numBytesVisible(files));
            buffer.append(bytes + " bytes in " + count + " files.");
            buffer.append("</div>");
        }
        return buffer.toString();
    }    
    
    
    private String imageTree(File parent, File selected) {
        File[] files = listFiles(parent);
        return imageTree(parent, files, null, selected);
    }    
    
    private String imageTree(File parent, File[] files, String[] titles, File selected) {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append(listDirectories(files, titles, selected));
        File[] imageFiles = getImages(files);
        int count = numFilesVisible(imageFiles);
        if (count > 0) {
            buffer.append("<div class='viewHeading'>");
            buffer.append(optionLinks(files, parent));            
            buffer.append("Listing images in " + parent);
            buffer.append("</div>");
            buffer.append(listImages(imageFiles));
            buffer.append("<div class='viewFooter'>");
            String bytes = formatNumber(numBytesVisible(files));
            buffer.append(bytes + " bytes in " + count + " files.");
            buffer.append("</div>");
        }        
        if (selected == null) {
            buffer.append("\r\njavascript:");
            buffer.append(updateImagesScript(files));
            buffer.append(updateIcon(constructID(parent)));
        }        
   
        return buffer.toString();
    }    
    
    private String errorResponse(String error) {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append("<div class='viewHeading'>");
        buffer.append(error);
        buffer.append("</div>");

        return buffer.toString();
    }     
    
    private String optionLinks(File[] files, File parent) {
        ArrayList<String> names = new ArrayList<String>();
        ArrayList<String> options = new ArrayList<String>();
        names.add("List");
        options.add("mode=list");          
        names.add("Details");
        options.add("mode=detail");          
        if (containsImage(files)) {
            names.add("Thumbnails");
            options.add("mode=gallery");
        }      
        return optionLinks(names.toArray(new String[names.size()]),
                           options.toArray(new String[options.size()]), parent);
    }
    
    private String optionLinks(String[] names, String[] options, File parent) {
        String[] js = new String[options.length];
        String id = constructID(parent);
        String href = WareHouse.getUrlPathForFile(parent);
        StringBuilder links = new StringBuilder();
        
        links.append("<div class='viewOptions'>[");        
        for (int i=0; i<options.length; i++) {
            js[i] = "javascript:" +
                    constructJavaScriptCall("showPath", 
                    new String[] { id, href, options[i] });
            links.append(" <a href='" + js[i] + "'>" + names[i] + "</a>");
        }
        links.append(" ]</div>");   

        return links.toString();
             
    }    
    
    private String listDirectories(File[] files, String[] titles, File selected) {    
        StringBuilder buffer = new StringBuilder();
        buffer.append("<div class='directoryList'>");
        for (int i=0; i<files.length; i++) {
            if (isDirectory(files[i]) && isVisible(files[i])) {
                String title = (titles == null) ? files[i].getName() : titles[i];
                buffer.append(getDirectoryElement(title, files[i], selected));                 
            }          
        }
        buffer.append("</div>");
        return buffer.toString();
    }  
    
    
    private String listFiles(File[] files, File selected) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("<div class='fileList'>");
        for (File file : files) {
            if (!isDirectory(file) && isVisible(file)) {
                boolean sel = isFileEquals(file, selected);
                buffer.append(getFileElement(file, resourcePath, sel));
            }            
        }
        buffer.append("</div>");
        return buffer.toString();
    }
    
    private String listFilesDetail(File[] files, File parent) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("<div class='fileDetailList'>");
        for (File file : files) {
            if (!isDirectory(file) && isVisible(file)) {
                buffer.append(getFileDetailElement(file, resourcePath));
            }               
        }
        buffer.append("</div>");
        return buffer.toString();
    }    
    
    private String listImages(File[] files) {
        StringBuilder buffer = new StringBuilder();
        buffer.append("<div class='fileImageList'>");
        for (File file : files) {
            buffer.append(getThumbnailElement(file));
        }
        buffer.append("</div>");
        return buffer.toString();
    }  
    
    private File[] getImages(File[] files) {
        ArrayList<File> imageFiles = new ArrayList<File>();
        for (File file : files) {
            if (!isDirectory(file) && isVisible(file)) {
                String type = WareHouse.getContentType(file);
                if (type != null && type.startsWith("image/")) {
                    imageFiles.add(file);
                }
            }               
        }
        return imageFiles.toArray(new File[imageFiles.size()]);
    }    
    
    private String updateImagesScript(File[] files) {
        StringBuilder buffer = new StringBuilder();
        for (File file : files) {
            if (!isDirectory(file) && isVisible(file)) {
                String type = WareHouse.getContentType(file);
                if (type != null && type.startsWith("image/")) {
                    String href = WareHouse.getUrlPathForFile(file);
                    String js = "javascript:" +
                                constructJavaScriptCall("showImage",
                                new String[] { constructID(file), href, "s=100", "s=500"
                    });
                    buffer.append(js);
                }
            }
        }
        return buffer.toString();
    }   
    
    private String updateIcon(String id) {
        StringBuilder js = new StringBuilder();
        js.append("imageElement = document.getElementById(\"" + id + "\" + \"_icon\");");
        js.append("imageElement.src = resourcePath + \"folder-open.png\";");
        return js.toString();
    }     
    
    private boolean containsImage(File[] files) {
        for (File file : files) {
            String type = WareHouse.getContentType(file);
            if (type != null && type.startsWith("image/")) {
                return true;
            }
        }
        return false;
    }  
    
    private String constructID(File file) {
        try {
            return "id_" + WareHouse.getMD5String(file.getCanonicalPath().toString());    
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        return file.toString();
    }
    
    private String constructJavaScriptCall(String methodName, String[] strings) {
        StringBuilder js = new StringBuilder();
        js.append(methodName + "(");
        for (int i=0; i<strings.length; i++) {
            js.append("\"" + strings[i] + "\"");
            if (i+1 < strings.length) js.append(", ");
        }
        js.append(");");
        return js.toString();
    }     
    
    private static boolean isEditable(File file) {
        String name = file.getName();
        String contentType = WareHouse.getContentType(file);
        
        if (name.startsWith(".")) return true;
        if (contentType != null && contentType.startsWith("text/")) {
            return true;
        }
        return false;
    } 
    
    private static boolean isVisible(File file) {
        try {
            if (file.canRead()) {
                // Unix style hidden file
                String name = file.getName();
                if (!name.equals(".") && name.startsWith(".")) return false;
                // Custom application data
                String contentType = WareHouse.getContentType(file);
                if (contentType != null && contentType.equals("application/wikiwebserver")) {
                    return false;
                }
                return true;
            }
        } catch (SecurityException ex) { }
        
        return false;
    }  
    
    private static boolean isDirectory(File dir) {
        try {
            if (dir == null) return false;
            if (!dir.exists()) return false;
            return dir.isDirectory();
        } catch (SecurityException ex) {
            // Thrown by org.wikiwebserver.core.SecurityMan
        } 
        return false;
    }
    
    private static File[] listFiles(File directory) {
        if (directory == null) return null;
        
        File[] files = directory.listFiles();
        Arrays.sort(files, new FileNameComparator());
        
        return files;
    }
    
    private static boolean isFileContainedIn(File file, File parent) {
        if (file == null || parent == null) return false;
        try {
            return file.getCanonicalPath().startsWith(parent.getCanonicalPath());
        } catch (Exception ex) { ex.printStackTrace(); }
        return false;
    }
    
    private static boolean isFileEquals(File file, File file2) {
        if (file == null || file2 == null) return false;
        try {
            return file.getCanonicalPath().equals(file2.getCanonicalPath());
        } catch (Exception ex) { ex.printStackTrace(); }
        return false;
    }    
    
    private static int numFilesVisible(File[] files) {
        int count = 0;
        for (File file : files) {
            if (isDirectory(file)) continue;
            if (isVisible(file)) count++;            
        }
        return count;
    } 
    
    private static long numBytesVisible(File[] files) {
        long count = 0;
        for (File file : files) {
            if (isDirectory(file)) continue;
            if (isVisible(file)) count += file.length();
        }
        return count;
    }     
    
    private static String formatNumber(long numeric) {
        return WareHouse.formatNumber(numeric);
    }

    public String getCacheKey() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }

    public long getExpireTime() {
        return System.currentTimeMillis() + 1000;
    }   
}

