import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.net.*;
import java.util.Properties;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
-rw-r--r--  1 matt  admin  3829 11 Nov 20:53 wiki4k-pre.jar
-rw-r--r--  1 matt  admin  3567 11 Nov 20:53 wiki4k.jar
 */
public class W {
    static Properties PAGES = new Properties();
    public static void main(String[] args) throws Exception {
        Properties pages = PAGES;
        Pattern requestProg = Pattern.compile(
                "^((GET)) /" + // 1,2
                        //URL
                        "(?:" +
                        // pagename/suffix
                        "(?:" +
                        "(([a-zA-Z0-9+]|%20)*)" + // 3,4
                        "((/edit)" + // 5,6
                        "|(\\?addcomment=([^ ]*))" + // 7,8
                        "|(/save\\?page=([^ ]*))" + // 9,10
                        ")?" +
                        ")" +
                        // recentchanges
                        "|(recent-changes(\\.rss)?)" + // 11,12
                        ")" +
                        // HTTP/1.1
                        " .*"
        );
        Pattern propertyNameProg = Pattern.compile("(.*)\\.ts");

        pages.setProperty("Home", "Welcome to wiki4k \u221e!");
        pages.setProperty("Home.ts", Long.toString(System.currentTimeMillis()));
        String storename = "wiki.properties";
        try {
            FileInputStream sin = new FileInputStream(storename);
            pages.load(sin);
            sin.close();
        } catch (Exception e) {
        }

        String homeurl = "http://localhost:" + args[0] + "/";

        ServerSocket server = new ServerSocket((int) Long.parseLong(args[0]));
        while (true) {
            Socket s = server.accept();
            DataInputStream in = new DataInputStream(s.getInputStream());
            String requestline = null;
            while (true) {
                String line = in.readLine();
//                System.out.println(line);
                if (line == null || line.equals("")) {
                    break;
                }
                if (requestline == null) {
                    requestline = line;
                }
            }

            // some tmp computations
            TreeMap<Long, String> recentChangesMap = new TreeMap<Long, String>();
            for (Object o : pages.keySet()) {
                String k = (String) o;
                Matcher tsm = propertyNameProg.matcher("");
                if (tsm.matches()) {
                    String g1 = tsm.group(1);
                    String value = pages.getProperty(k);
                    if (g1 != null) {
                        long ts = Long.parseLong(value);
                        recentChangesMap.put(new Long(-ts), g1);
                    }
                }
            }

            OutputStreamWriter out = new OutputStreamWriter(s.getOutputStream(), "UTF-8");
            Matcher m = requestProg.matcher(requestline);
            out.write("HTTP/1.1 ");

            if (m.matches()) {
                if (m.group(11) != null) {
                    boolean rss = m.group(12) != null;
                    out.write("200 OK\r\nContent-Type: "); // TODO reuse with else below?
                    out.write(rss ? "application/rss+xml" : "text/html");
                    out.write("; charset=UTF-8\r\n\r\n");

                    if (rss) {
                        out.write("<?xml version=\"1.0\"?>\n<rss version=\"2.0\"><channel><title>wiki4k</title><link>");
                        out.write(homeurl);
                        out.write("</link><description>wiki4k</description>");
                    }

                    if (!rss) {
                        out.write("<h1>Recent Changes</h1><ul>");
                    }
                    for (String spagename : recentChangesMap.values()) {
                        out.write(rss? "<item><link>" : "<li><a href='");
                        if (rss) {
                            out.write(homeurl);
                        }
                        out.write(URLEncoder.encode(spagename, "UTF-8"));
                        out.write(rss? "</link>" : "'>");
                        out.write(rss? "<title>" : "");
                        out.write(spagename);
                        out.write(rss? "</title></item>" : "</a>");
                    }

                    if (rss) {
                        out.write("</channel></rss>");
                    }

                } else {
                    String upagename = m.group(3); // url page name
                    if (upagename.equals("")) {
                        upagename = "Home";
                    }
                    String spagename = URLDecoder.decode(upagename, "UTF-8"); // decoded pagename

                    String g8 = m.group(8);
                    String g9 = m.group(9);
                    if (g9 != null || g8 != null) {
                        if (g9 != null) {
                            // save page
                            String newtext = URLDecoder.decode(m.group(10), "UTF-8");
                            pages.setProperty(spagename, newtext);
                            pages.setProperty(spagename + ".ts", Long.toString(System.currentTimeMillis()));
                        } else {
                            // add comment
                            String newcomment = URLDecoder.decode(g8, "UTF-8");
                            String key = spagename + ".comments";
                            String comments = pages.getProperty(key);
                            if (comments == null) {
                                comments = newcomment;
                            } else {
                                comments = comments + "\u001b" + newcomment;
                            }
                            pages.setProperty(key, comments);
                        }

                        out.write("307 l8r\r\nLocation: /");
                        out.write(upagename);
                        out.write("\r\n\r\n");

                        FileOutputStream sout = new FileOutputStream(storename);
                        pages.store(sout, spagename);
                        sout.close();
                    } else {
                        out.write("200 OK\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n");
                        out.write("<h1>");
                        out.write(spagename);
                        out.write("</h1>");

                        String page = pages.getProperty(spagename);

                        if (m.group(6) != null) {
                            // page edit
                            if (page == null) {
                                page = "All about " + spagename;
                            }
                            out.write("<form action='save'><textarea name='page' rows='15' cols='80' accept-charset='UTF-8'>");
                            out.write(e(page));
                            out.write("</textarea><br><input type='submit' value='Save'>" +
                                    " (<strong>*bold*</strong> <em>_italics_</em> [Page Link])");
                        } else {
                            // page view
                            if (page == null) {
                                page = "Unknown page" + spagename;
                            }
                            out.write("<p>");
                            w(out, page);
                            out.write("<hr> " +
                                    "<a href='/recent-changes'>Recent Changes</a> " +
                                    "(<a href='/recent-changes.rss'>rss</a>) " +
                                    "<a href='/");
                            out.write(upagename);
                            out.write("/edit'>Edit Page</a><br>Comments:<ul>");

                            String comments = pages.getProperty(spagename + ".comments", "");
                            for (String cmt : comments.split("\u001b")) {
                                if (cmt.length() > 0) {
                                    out.write("<li>");
                                    w(out, cmt);
                                }
                            }

                            out.write("</ul>"+
                                    "<a href='#' onclick=\"getElementById('ac').style.display='block'\">Add Comment</a>" +
                                    "<form id='ac' action='' style='display: none'>" +
                                    "<textarea name='addcomment' rows='15' cols='80' accept-charset='UTF-8'>" +
                                    "</textarea><br><input type='submit' value='Save'>");
                        }
                    }
                }


            } else {
                out.write("500 wtf\r\n\r\n");
            }
            out.close();

            s.close();
        }
    }

    /** wiki escape the page into the stream */
    public static void w(OutputStreamWriter out, final String page) throws Exception {
        Properties pages = PAGES;

        Pattern allprog = Pattern.compile("" +
                "(\\[([a-zA-Z0-9 ]+)\\])" + // 1, 2=links
                "|((?m:^)([*]+) (.*)(?m:\r\n|\r|\n|$))" + // 3 lists 4=indents 5=body
                "|(([*_+^~-])((?!\\7).+?)\\7)" + // 6  7=style 8=stylebody
                "|((?:\r\n|\r|\n)((?:\r\n|\r|\n)*))" + // 9=newline, 10=morenrelines
                "|(\\{\\{(.+?)\\}\\})" + // 11 code 12=body
                "|((?m:^)(?:(?:(h[1-6])|(bq))\\.) +(.*)(?m:\r\n|\r|\n|$))" + // 13  14=headings, 15=bq, 16=body
                "|((?m:^) *-{4,} *(?m:\r\n|\r|\n|$))" + // 17 --- hr
                "|(\\{([a-zA-Z]+)\\})((?!\\18)(?s:.)+)\\18" + // {macro}...{macro} 18 macro 19 macroname 20 body
                "|(\\\\)?(.)" // 21=escapenext 22 everything else
        );

        Matcher m = allprog.matcher(page);
        int offset = 0;
        int listLevel = 0;
        int endLength = page.length();
        TOP:
        while (offset < endLength) {
            String tag = null;
            boolean noEndTag = false;
            String href = null;
            String innerHtml = null;
            String innerWiki = null;
            String innerText = null;
            int newListLevel = 0;

            int group = matchgroup(m, offset, endLength);
            switch (group) {
                case 1: // links
                    String spagename = m.group(2);
                    tag = "a";
                    href = spagename;
                    innerHtml = spagename;
                    break;
                case 3:
                    tag = "li";
                    newListLevel = m.group(4).length();
                    innerWiki = m.group(5);
                    break;
                case 6: // *bold* etc styles
                    char c = m.group(7).charAt(0);
                    switch (c) {
                        case '*' : tag = "strong"; break;
                        case '_' : tag = "em"; break;
                        case '+' : tag = "u"; break;
                        case '^' : tag = "sup"; break;
                        case '~' : tag = "sub"; break;
                        case '-' : tag = "del"; break;
                    }
                    innerWiki = m.group(8);
                    break;
                case 9:
                    innerHtml = m.group(10).equals("") ? " " : "<p>";
                    break;
                case 11: // {{code}}
                    tag = "code";
                    innerText = m.group(12);
                    break;
                case 13: // h1 etc
                    tag = m.group(14);
                    if (tag == null) {
                        tag = "blockquote";
                    }
                    innerWiki = m.group(16);
                    break;
                case 17: // ----
                    tag = "hr";
                    noEndTag = true;
                    break;
                case 18:
                    //comment in/out for macro support
                {

                    String macname = m.group(19);
                    try {
                        String classname = pages.getProperty("macro." + macname);
                        Class<?> clazz = W.class.getClassLoader().loadClass(classname);
                        FileNameMap macro = (FileNameMap) clazz.newInstance();
                        innerHtml = macro.getContentTypeFor(m.group(20));
                        //TODO what to do about if not found, print macronotfound?
                    } catch (Exception e) {
//                        e.printStackTrace();
                        tag = "code";
                        innerText = "MACROERROR:" + macname;
                    }
                }
                    break;
                case 21:
                default:
                    innerText = m.group(22);
            }
            offset = m.end();

            while (listLevel < newListLevel) {
                out.write("<ul>");
                listLevel++;
            }
            while (listLevel > newListLevel) {
                out.write("</ul>");
                listLevel--;
            }

            if (tag != null) {
                out.write("<");
                out.write(tag);
                if (href != null) {
                    out.write(" href='");
                    out.write(URLEncoder.encode(href, "UTF-8"));
                    out.write("'");
                }
                out.write(">");
            }
            if (innerWiki != null) {
                w(out, innerWiki);
            }
            if (innerHtml != null) {
                out.write(innerHtml);
            }
            if (innerText != null) {
                out.write(e(innerText));
            }
            if ((tag != null) && !noEndTag) {
                out.write("</");
                out.write(tag);
                out.write(">");

            }

        }

        while (listLevel > 0) {
            out.write("</ul>");
            listLevel--;
        }

    }

    private static int matchgroup(Matcher m, int offset, int endLength) {
        m.useTransparentBounds(true);
        m.useAnchoringBounds(false);
        m.region(offset, endLength);
        if (!m.lookingAt()) {
            return 0;
        }
        int l = m.groupCount();
        for (int i = 1; i < l; i++) {
            String g = m.group(i);
            if (g != null) {
                return i;
            }
        }
        return 0;
    }

    /** html escape the input */
    public static String e(String page) {
        return page.replaceAll("&", "&amp;")
                .replaceAll(">", "&gt;")
                .replaceAll("<", "&lt;")
                .replaceAll("\"", "&quot;")
                .replaceAll("'", "&apos;");
    }
}

