package org.argosdic.dictionary;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TimeZone;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.argosdic.resource.ResourceManager;
import org.argosdic.util.MimeTypeMap;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.util.Assert;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

/**
 * ServerWorker.java
 * @author Xavier Cho
 * @version $Revision: 1.6 $ $Date: 2003/10/19 14:41:35 $
 */
public class ServerWorker implements Runnable {
    public static final long EXPIRATION = 10 * 365 * 24 * 60 * 60 * 1000;
    private static Log log = LogFactory.getLog(ServerWorker.class);

    private DictionaryServer server;
    private ServerSocket socket;
    private String htmlDir;
    private String serverName;
    private MimeTypeMap mimeMap;
    private String systemCharset;
    private DateFormat df;
    private int id;

    protected ServerWorker(
        DictionaryServer server,
        ServerSocket socket,
        int id) {
        Assert.isNotNull(server);
        Assert.isNotNull(socket);

        this.id = id;
        this.server = server;
        this.socket = socket;
        this.htmlDir = server.getHtmlDir();
        this.mimeMap = MimeTypeMap.getInstance();

        ResourceManager resources = ResourceManager.getInstance();

        this.systemCharset = resources.getString("default.charset"); //$NON-NLS-1$
        this.serverName = resources.getString("http.server"); //$NON-NLS-1$
        this.df = new SimpleDateFormat("E, dd M yyyy HH:mm:ss z", Locale.ENGLISH); //$NON-NLS-1$
        df.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
    }

    private String getResponseHeader(int code, String msg) {
        return null;
    }

    public void run() {
        BufferedReader reader = null;
        OutputStream out = null;
        String word = null;

        while (!socket.isClosed()) {
            try {
                Socket clientSocket = socket.accept();

                if (log.isDebugEnabled()) {
                    log.debug("Accepting new client - worker: " + id); //$NON-NLS-1$
                }

                InputStream in = clientSocket.getInputStream();
                out = clientSocket.getOutputStream();

                reader = new BufferedReader(new InputStreamReader(in));

                String resource = null;

                Date ifModifiedSince = null;

                String line = null;
                while ((line = reader.readLine()) != null) {
                    if (line.trim().length() == 0) {
                        break;
                    }

                    if (log.isDebugEnabled()) {
                        log.debug("Request header : " + line); //$NON-NLS-1$
                    }

                    if (line.startsWith("GET")) { //$NON-NLS-1$
                        StringTokenizer tokenizer = new StringTokenizer(line);
                        if (tokenizer.countTokens() > 2) {
                            tokenizer.nextToken();
                            resource = tokenizer.nextToken();

                            if (resource.startsWith("/")) { //$NON-NLS-1$
                                resource = resource.substring(1);
                            }
                        }
                    } else if (line.toLowerCase().startsWith("if-modified-since")) { //$NON-NLS-1$
                        String value = line.substring(line.indexOf(":") + 1).trim(); //$NON-NLS-1$
                        try {
                            ifModifiedSince = df.parse(value);
                        } catch (Exception e) {
                            if (log.isWarnEnabled()) {
                                log.warn("Unable to parse date header : " + value, //$NON-NLS-1$
                                e);
                            }
                        }
                    }
                }

                resource = URLDecoder.decode(resource, systemCharset);

                if (log.isDebugEnabled()) {
                    log.debug("Finished reading client request - worker : " + id); //$NON-NLS-1$
                }

                if (resource != null) {
                    IndexSearcher searcher = server.getSearcher();

                    if (resource.endsWith(".dic") && searcher != null) { //$NON-NLS-1$
                        word = resource.substring(0, resource.length() - 4);

                        if (log.isDebugEnabled()) {
                            log.debug("Searching for : " + word); //$NON-NLS-1$
                        }

                        SearchHistory history = server.getSearchHistory();
                        Document document = history.get(word);

                        if (document == null) {
                            Hits hits = server.search(word);
                            int matches = hits.length();

                            if (matches > 0) {
                                for (int i = 0; i < matches; i++) {
                                    Document doc = hits.doc(i);

                                    if (word.equalsIgnoreCase(doc.getField("word").stringValue())) { //$NON-NLS-1$
                                        document = doc;
                                        break;
                                    }
                                }

                                if (document == null) {
                                    document = hits.doc(0);
                                }
                            }
                        }

                        if (document != null) {
                            if (log.isDebugEnabled()) {
                                log.debug("Writing server response... - worker : " + id); //$NON-NLS-1$
                            }

                            Map headers = getDefaultHeaders();
                            headers.put("Cache-Control", "no-cache"); //$NON-NLS-1$ //$NON-NLS-2$

                            String header = server.getHttpHeader(DictionaryServer.STATUS_OK, "OK", headers); //$NON-NLS-1$

                            FontData font = server.getFont();

                            Object[] args = new String[] { word, server.getDictionary().getCharset(), font.getName(), Integer.toString(font.getHeight()), document.getField("definition") //$NON-NLS-1$
                                .stringValue()};

                            out.write(header.getBytes()); //$NON-NLS-1$
                            out.write(server.getHtmlContent(args).getBytes());
                        }
                    } else {
                        if (log.isDebugEnabled()) {
                            log.debug("Processing resource : " + resource); //$NON-NLS-1$
                        }

                        StringBuffer buffer = new StringBuffer(htmlDir);
                        buffer.append(File.separator);
                        buffer.append(
                            resource.replace('/', File.separatorChar));

                        File file = new File(buffer.toString());

                        if (log.isDebugEnabled()) {
                            log.debug("File name : " + file.getAbsolutePath()); //$NON-NLS-1$
                        }

                        if (!file.exists() || file.isDirectory()) {
                            if (log.isWarnEnabled()) {
                                log.warn("The specified file has not been found : " //$NON-NLS-1$
                                +file.getAbsolutePath());
                            }

                            String header = server.getHttpHeader(DictionaryServer.STATUS_NOT_FOUND, "NOT FOUND"); //$NON-NLS-1$

                            out.write(header.getBytes());
                            out.write("The specified resource has not been found." //$NON-NLS-1$
                            .getBytes());
                        } else {
                            Date lastModified = new Date(file.lastModified());

                            if (ifModifiedSince != null
                                && !ifModifiedSince.before(lastModified)) {

                                if (log.isDebugEnabled()) {
                                    log.debug("Using cached content : " + file.getAbsolutePath()); //$NON-NLS-1$
                                }

                                String header = server.getHttpHeader(DictionaryServer.STATUS_NOT_MODIFIED); //$NON-NLS-1$
                                out.write(header.getBytes());
                            } else {
                                if (log.isDebugEnabled()) {
                                    log.debug("Writing file content..."); //$NON-NLS-1$
                                }

                                String contentType =
                                    mimeMap.getContentTypeFor(resource);
                                String length = Long.toString(file.length());

                                Date expires =
                                    new Date(
                                        System.currentTimeMillis()
                                            + EXPIRATION);

                                Map headers = getDefaultHeaders();
                                headers.put("Last-Modified", //$NON-NLS-1$
                                df.format(lastModified));
                                headers.put("Accept-Ranges", "bytes"); //$NON-NLS-1$ //$NON-NLS-2$
                                headers.put("Content-Type", contentType); //$NON-NLS-1$
                                headers.put("Content-Length", length); //$NON-NLS-1$
                                headers.put("Expires", df.format(expires)); //$NON-NLS-1$

                                    String header = server.getHttpHeader(DictionaryServer.STATUS_OK, "OK", //$NON-NLS-1$
    headers);

                                out.write(header.getBytes());

                                DataOutputStream dout = null;
                                InputStream fin = null;

                                try {
                                    in =
                                        new DataInputStream(
                                            new FileInputStream(file));
                                    in = new BufferedInputStream(in);

                                    out = new BufferedOutputStream(out);
                                    dout = new DataOutputStream(out);

                                    int data = -1;
                                    while ((data = in.read()) != -1) {
                                        dout.writeByte(data);
                                    }

                                    dout.flush();
                                } finally {
                                    if (fin != null) {
                                        try {
                                            fin.close();
                                        } catch (IOException ie) {
                                        }
                                    }

                                    if (dout != null) {
                                        try {
                                            dout.close();
                                        } catch (IOException ie) {
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (ParseException e) {
                ResourceManager resources = ResourceManager.getInstance();

                Shell parent = Display.getCurrent().getActiveShell();
                String title = resources.getString("dialog.title.warning"); //$NON-NLS-1$
                String msg = resources.getString("error.message.query"); //$NON-NLS-1$

                MessageDialog.openWarning(parent, title, msg);
            } catch (SocketException e) {
                if (!socket.isClosed()) {
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (reader != null) {
                    try {
                        reader.close();
                    } catch (IOException e) {
                    }
                }

                if (out != null) {
                    try {
                        out.close();
                    } catch (Exception e) {
                    }
                }
            }
        }
    }

    private Map getDefaultHeaders() {
        Date currentTime = new Date(System.currentTimeMillis());
        Map headers = new HashMap(10);

        headers.put("Connection", "close"); //$NON-NLS-1$ //$NON-NLS-2$
        headers.put("Server", serverName); //$NON-NLS-1$
        headers.put("Date", df.format(currentTime)); //$NON-NLS-1$

        return headers;
    }
}
