package org.wikiwebserver.handler.http.responder;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.StringTokenizer;

import org.wikiwebserver.core.ConfigManager;
import org.wikiwebserver.core.WareHouse;
import org.wikiwebserver.handler.http.HTTPException;
import org.wikiwebserver.handler.http.HTTPHandler;
import org.wikiwebserver.handler.http.HTTPOutputStream;
import org.wikiwebserver.handler.http.interfaces.CacheableHTTPResponse;
import org.wikiwebserver.handler.http.interfaces.HTTPResponder;

public class FileResponder implements HTTPResponder, CacheableHTTPResponse {
	
	private static final long DEFAULT_FILE_CACHE_TIME =
		ConfigManager.getLong("http.file-expiration"); 
	
	private static final int MAX_CACHEABLE_FILE_SIZE = 
		ConfigManager.getInt("http.max-cacheable-file-size"); 
	
	private static final int READ_BUFFER_SIZE = 
		ConfigManager.getInt("http.file-read-buffer-size"); 
	
	private String path;
	private String key;
	
	private File file;
	private long skip = 0;
	private long end = -1;
	private long length = -1;
	
	
	public FileResponder(String path) {
		this.path = path;
	}
	
	public FileResponder(File file) {
		this.file = file;
	}	
	
	public void init(HTTPHandler conn) throws IOException {

        if (path != null) {
            this.key = this.path;
        }
        else if (file != null) {
            this.key = this.file.toString();
        }
        
        String contentType = WareHouse.getContentType(this.path);
        conn.getResponse().getHeaders().set("Content-Type", contentType);
        
        String rs = conn.getRequest().getHeaders().getFirst("Range");
        if (rs != null) {
            
            File file = getFileFromURL(conn.getRequest().getUrl());
            length = file.length();
            skip = 0;
            end = length-1;
    
            int idx = rs.indexOf("bytes=");
            if (idx > -1) {
                rs = rs.substring(idx+6);
                idx = rs.indexOf('-');
                if (idx > 0) {
                    skip = Integer.parseInt(rs.substring(0, idx));
                }
                if (rs.length() > idx+1) {
                    end = Integer.parseInt(rs.substring(idx+1));
                }  
                length = (end+1) - skip;
            }
            if (skip < 0 || end >= file.length() || end <= skip) {
                throw new HTTPException(416, "Requested range can not be satisfied");
            }
            else {
                conn.getResponse().setCode(206);
                conn.getResponse().setInfo("Parital content");
                String cr = "bytes " + skip + "-" + end + "/" + file.length();
                conn.getResponse().getHeaders().set("Content-Range", cr);
            }   
            
            key += "[" + skip + "," + length + "]";            
        }
	}	

	public Object respond(HTTPHandler conn) throws IOException {
		
		//System.out.println("Cache miss for File: " + path);  
		
		if (file == null) {
			file = getFileFromURL(path);
		}
		
		// Will use class loader to try and download file
        if (!file.exists()) {
            file = WareHouse.getResourceFile(path.substring(1));
            if (file == null) {
                throw new HTTPException(404, "File not found: " + path.substring(1));                
            }
        }    		
		
		if (file.isDirectory()) {
            
		    File[] indexFiles = { 
                    new File(file, "index.html")
            };
            
            for (File indexFile : indexFiles) {
                if (indexFile.exists()) {
                    if (!path.endsWith("/")) {
                        // Redirect to directory reference
                        String dirBaseUrl = conn.getServiceAddress() + path + "/";
                        throw new HTTPException(301, "You missed the slash", dirBaseUrl);
                    }
                    file = indexFile;                    
                }
            }
        }
		
		if (file.isDirectory()) {
            throw new HTTPException(403, "Directory exists but no index found");		    
		}
        
        // File not found anywhere
        if (!file.exists()) {
        	throw new HTTPException(404, "Object not found: " + file);
        }
        
        if (length == -1) length = file.length();
        
        conn.getResponse().getHeaders().setDate("Last-Modified", file.lastModified());
        conn.getResponse().getHeaders().setDate("Expires", getExpireTime());    
        conn.getResponse().getHeaders().set("Content-Length", String.valueOf(length));                
        
        HTTPOutputStream out = conn.getOutputStream();
        
        out.prepairStream();
        if (out.contentExpected()) {

            int bufferSize = READ_BUFFER_SIZE;
            
            if (length <= MAX_CACHEABLE_FILE_SIZE) {
                bufferSize = (int) length;
            }
            
        	byte[] buffer = new byte[bufferSize];

        	InputStream fin = null;
        	try {
                fin = new FileInputStream(file);
                fin.skip(skip);
                
                int p = -1;
                int len = fin.read(buffer);
    
                // Return the buffer (which may be cached)
                if (len == length) {
                	return buffer;
                } 
            	else {
            		if (end == -1) end = length-1;
    
            		conn.getResponse().getHeaders().set("Accept-Ranges", "bytes");

                    // Otherwise stream the data, according to buffer size
                    while (len > 0) {
                        p += len;
                        if (p > end) len = (int)(end-p);
                        out.write(buffer, 0, len);
                        if (p == end) break;
                        len = fin.read(buffer);
                    }
                }
        	}
            catch (FileNotFoundException ex) {
                HTTPException httpe = new HTTPException(404, "File not found: " + file);
                httpe.initCause(ex);
                throw httpe;
            }
            catch (IOException ex) {
                HTTPException httpe = new HTTPException(500, "Failed to transfer file: " + file);
                httpe.initCause(ex);
                throw httpe;            
            }
            catch (Exception ex) {
                HTTPException httpe = new HTTPException(500, "Serious problem transferring file: " + file);
                httpe.initCause(ex);
                throw httpe;            
            } finally {
                try {
                    if (fin != null) fin.close();
                } catch (Exception ex) { /* dead already */ }
            }            	
        }


        return null;
	}

	public String getCacheKey() {
		return this.key;
	}

	public long getExpireTime() {
		return System.currentTimeMillis() + DEFAULT_FILE_CACHE_TIME;
	}

	
	
    
    public static File getFileFromURL(String url) {
        
        if (url == null) return null;
        // Strip server
        if (url.startsWith("http://") && url.length() > 7) {
            int idx = url.indexOf('/', 7);
            if (idx > -1) {
                url = url.substring(idx);
            }
        }
        
        if (url.equals("/")) return new File(".");
        
        StringBuilder fileBuilder = new StringBuilder();
        
        StringTokenizer t = new StringTokenizer(url, "/");
        while (t.hasMoreTokens()) {
            fileBuilder.append(WareHouse.urlDecode(t.nextToken()));
            if (t.hasMoreTokens()) {
                fileBuilder.append(File.separator);
            }
        }        
        
        return new File(fileBuilder.toString());
    }	  
}

