// Bookmarks_p.java
// -----------------------
// part of YACY
// (C) by Michael Peter Christen; mc@yacy.net
// first published on http://www.anomic.de
// Frankfurt, Germany, 2004
//
// This File is contributed by Alexander Schier
//
// $LastChangedDate$
// $LastChangedRevision$
// $LastChangedBy$
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// You must compile this file with
// javac -classpath .:../Classes Blacklist_p.java
// if the shell's current path is HTROOT

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import net.yacy.cora.date.ISO8601Formatter;
import net.yacy.cora.document.encoding.ASCII;
import net.yacy.cora.document.id.DigestURL;
import net.yacy.cora.federate.yacy.CacheStrategy;
import net.yacy.cora.protocol.ClientIdentification;
import net.yacy.cora.protocol.HeaderFramework;
import net.yacy.cora.protocol.RequestHeader;
import net.yacy.cora.util.ConcurrentLog;
import net.yacy.data.BookmarkHelper;
import net.yacy.data.BookmarksDB;
import net.yacy.data.ListManager;
import net.yacy.data.UserDB;
import net.yacy.data.BookmarksDB.Bookmark;
import net.yacy.data.BookmarksDB.Tag;
import net.yacy.document.Document;
import net.yacy.document.Parser;
import net.yacy.http.servlets.YaCyDefaultServlet;
import net.yacy.kelondro.data.meta.URIMetadataNode;
import net.yacy.kelondro.workflow.BusyThread;
import net.yacy.peers.NewsPool;
import net.yacy.search.AutoSearch;
import net.yacy.search.Switchboard;
import net.yacy.server.serverObjects;
import net.yacy.server.serverSwitch;


public class Bookmarks {

	private static final serverObjects prop = new serverObjects();
	private static Switchboard sb = null;
	private static UserDB.Entry user = null;
	private static boolean isAdmin = false;

	final static int SORT_ALPHA = 1;
	final static int SORT_SIZE = 2;
	final static int SHOW_ALL = -1;
	final static boolean TAGS = false;
	final static boolean FOLDERS = true;

        final static float TAGCLOUD_FONTSIZE_MIN = 0.75f; // min font-size in em
        final static float TAGCLOUD_FONTSIZE_MAX = 2.0f; // max font-size in em

    public static serverObjects respond(final RequestHeader header, final serverObjects post, final serverSwitch env) {

    	int max_count = 10;
    	int start=0;
    	int display = 0;
    	String tagName = "";
    	String username="";

    	prop.clear();
    	sb = (Switchboard) env;
    	user = sb.userDB.getUser(header);
    	isAdmin = (sb.verifyAuthentication(header) || user!= null && user.hasRight(UserDB.AccessRight.BOOKMARK_RIGHT));

    	// set user name
    	if (user != null) {
            username=user.getUserName();
        } else if(isAdmin) {
            username="admin";
        }
    	prop.putHTML("user", username);

    	//redirect to userpage
    	/*
    	if(username!="" &&(post == null || !post.containsKey("user") && !post.containsKey("mode")))
        prop.put("LOCATION", "/Bookmarks.html?user="+username);
    	*/

    	// set peer base URL : used by Bookmars.rss to render an absolute URL
    	prop.put("address", YaCyDefaultServlet.getContext(header, sb));

    	//defaultvalues
    	if(isAdmin) {
            prop.put("mode", "1");
            prop.put("admin", "1");
            prop.put("display", "0");
        } else {
            prop.put("mode", "0");
            prop.put("admin", "0");
            prop.put("display", "0");
        }
    	prop.put("mode_edit", "0");
    	prop.put("mode_title", "");
    	prop.put("mode_description", "");
    	prop.put("mode_url", "");
    	prop.put("mode_tags", "");
    	prop.put("mode_path", "");
    	prop.put("mode_public", "1"); //1=is public
    	prop.put("mode_feed", "0"); //no newsfeed

    	if (post != null) {
            if (!isAdmin) {
                if(post.containsKey("login")){
                	prop.authenticationRequired();
                }
            } else if (post.containsKey("mode")) {
                final String mode=post.get("mode");
                if ("add".equals(mode)) {
                    prop.put("mode", "2");
                    prop.put("display", "1");
                    display = 1;
                } else if ("importxml".equals(mode)){
                    prop.put("mode", "3");
                    prop.put("display", "1");
                    display = 1;
                }
            } else if(post.containsKey("add")) { //add an Entry
                final String url=post.get("url");
                final String title=post.get("title");
                final String description=post.get("description");
                String tagsString = post.get("tags");
                String pathString = post.get("path");
                if(pathString == null || "".equals(pathString)){
                    pathString="/unsorted"; //default folder
                }
                tagsString = tagsString + "," + pathString;
                final Set<String> tags=ListManager.string2set(BookmarkHelper.cleanTagsString(tagsString));
                final BookmarksDB.Bookmark bookmark = sb.bookmarksDB.createorgetBookmark(url, username);

                if (bookmark != null) {
                    bookmark.setProperty(BookmarksDB.Bookmark.BOOKMARK_TITLE, title);
                    bookmark.setProperty(BookmarksDB.Bookmark.BOOKMARK_DESCRIPTION, description);

                    if (user!=null) {
                        bookmark.setOwner(user.getUserName());
                    }

                    if ("public".equals(post.get("public"))) {
                        bookmark.setPublic(true);
                        publishNews(url, title, description, tagsString);
                    } else {
                        bookmark.setPublic(false);
                    }

                    if(post.containsKey("feed") && ("true".equals(post.get("feed")))){
                        bookmark.setFeed(true);
                    } else {
                        bookmark.setFeed(false);
                    }

                    bookmark.setTags(tags, true);
                    
                    if (post.containsKey("query")) {
                        bookmark.setProperty(Bookmark.BOOKMARK_QUERY, post.get("query"));
                        bookmark.setTimeStamp(System.currentTimeMillis());
                    }
                    sb.bookmarksDB.saveBookmark(bookmark);
                }

            } else if (post.containsKey("edit")) {
                final String urlHash = post.get("edit");
                prop.put("mode", "2");
                prop.put("display", "1");
                display = 1;
                ClientIdentification.Agent agent = ClientIdentification.getAgent(post.get("agentName", ClientIdentification.yacyInternetCrawlerAgentName));
                if (urlHash.isEmpty()) {
                    prop.put("mode_edit", "0"); // create mode
                    prop.putHTML("mode_title", post.get("title"));
                    prop.putHTML("mode_description", post.get("description"));
                    prop.putHTML("mode_url", post.get("url"));
                    prop.putHTML("mode_tags", post.get("tags"));
                    prop.putHTML("mode_path", post.get("path"));
                    prop.put("mode_public", "0");
                    prop.put("mode_feed", "0");
                } else {
                    BookmarksDB.Bookmark bookmark = sb.bookmarksDB.getBookmark(urlHash);

                    if (bookmark == null) {
                        // try to get the bookmark from the LURL database
                        final URIMetadataNode urlentry = sb.index.fulltext().getMetadata(ASCII.getBytes(urlHash));
                        if (urlentry != null) try {
                            final Document document = Document.mergeDocuments(urlentry.url(), null, sb.loader.loadDocuments(sb.loader.request(urlentry.url(), true, false), CacheStrategy.IFEXIST, Integer.MAX_VALUE, null, agent));
                            prop.put("mode_edit", "0"); // create mode
                            prop.put("mode_url", urlentry.url().toNormalform(false));
                            prop.putHTML("mode_title", urlentry.dc_title());
                            prop.putHTML("mode_description", (document == null) ? urlentry.dc_title(): document.dc_title());
                            prop.putHTML("mode_author", urlentry.dc_creator());
                            prop.putHTML("mode_tags", (document == null) ? urlentry.dc_subject() : document.dc_subject(','));
                            prop.putHTML("mode_path","");
                            prop.put("mode_public", "0");
                            prop.put("mode_feed", "0"); //TODO: check if it IS a feed
                        } catch (final IOException e) {ConcurrentLog.logException(e);} catch (final Parser.Failure e) {ConcurrentLog.logException(e);}
                    } else {
                        // get from the bookmark database
                        prop.put("mode_edit", "1"); // edit mode
                        prop.putHTML("mode_title", bookmark.getTitle());
                        prop.putHTML("mode_description", bookmark.getDescription());
                        prop.put("mode_url", bookmark.getUrl()); //TODO: XSS protection - how is this stored?
                        prop.putHTML("mode_tags", bookmark.getTagsString());
                        prop.putHTML("mode_path",bookmark.getFoldersString());

                        if (bookmark.getQuery() != null) {
                            prop.put("mode_hasquery","1");
                            prop.putHTML("mode_hasquery_query", bookmark.getQuery());
                        } else {
                            prop.put("mode_hasquery", "0");
                        }

                        if (bookmark.getPublic()) {
                            prop.put("mode_public", "1");
                        } else {
                            prop.put("mode_public", "0");
                        }

                        if (bookmark.getFeed()) {
                            prop.put("mode_feed", "1");
                        } else {
                            prop.put("mode_feed", "0");
                        }
                    }
                }
            } else if(post.containsKey("htmlfile")){
                final boolean isPublic = "public".equals(post.get("public"));

                String tags = post.get("tags");
                if("".equals(tags)){
                    tags="unsorted";
                }

                ConcurrentLog.info("BOOKMARKS", "Trying to import bookmarks from HTML-file");

                try {
                    final File file = new File(post.get("htmlfile"));
                    BookmarkHelper.importFromBookmarks(sb.bookmarksDB, new DigestURL(file), post.get("htmlfile$file"), tags, isPublic);
                } catch (final MalformedURLException e) {}

                ConcurrentLog.info("BOOKMARKS", "success!!");

            } else if (post.containsKey("xmlfile")) {

                final boolean isPublic = "public".equals(post.get("public"));
                BookmarkHelper.importFromXML(sb.bookmarksDB, post.get("xmlfile$file"), isPublic);

            } else if (post.containsKey("delete")) {

                final String urlHash=post.get("delete");
                sb.bookmarksDB.removeBookmark(urlHash);

            }

            if (post.containsKey("tag")) {
                tagName = post.get("tag");
            }

            if (post.containsKey("start")) {
                start = post.getInt("start", 0);
            }

            if (post.containsKey("num")) {
                max_count = post.getInt("num", 10);
            }
    	} // END if(post != null)

    	if (display == 0) {

	    	//-----------------------
	    	// create tag list
	    	//-----------------------
	    	printTagList("taglist", tagName, SORT_SIZE, 25, false);
	    	printTagList("optlist", tagName, SORT_ALPHA, SHOW_ALL, true);

	    	//-----------------------
	    	// create bookmark list
	    	//-----------------------
	    	int count = 0;
	        Iterator<String> it = null;

                prop.put("display_num-bookmarks", sb.bookmarksDB.bookmarksSize());

	       	if(!"".equals(tagName)){
                    prop.put("display_selected", "");
                    it = sb.bookmarksDB.getBookmarksIterator(tagName, isAdmin);
	       	} else {
                    prop.put("display_selected", " selected=\"selected\"");
                    it = sb.bookmarksDB.getBookmarksIterator(isAdmin);
	       	}

	       	//skip the first entries (display next page)
	       	count = 0;
	       	while(count < start && it.hasNext()){
                    it.next();
                    count++;
	       	}

	       	count = 0;
	       	while(count < max_count && it.hasNext()) {
                    Bookmark bookmark = sb.bookmarksDB.getBookmark(it.next());

                    if (bookmark != null){
                        if (bookmark.getFeed() && isAdmin) {
                            prop.putXML("display_bookmarks_"+count+"_link", "/FeedReader_p.html?url="+bookmark.getUrl());
                        } else {
                            prop.putXML("display_bookmarks_"+count+"_link",bookmark.getUrl());
                        }
                        prop.putHTML("display_bookmarks_"+count+"_title", bookmark.getTitle());
                        prop.putHTML("display_bookmarks_"+count+"_description", bookmark.getDescription());
                        if (bookmark.getQuery() == null) {
                            prop.put("display_bookmarks_"+count+"_hasquery", false);
                        } else {
                            prop.put("display_bookmarks_"+count+"_hasquery", true);
                            prop.put("display_bookmarks_"+count+"_hasquery_query", bookmark.getQuery());
                        }
                        prop.put("display_bookmarks_"+count+"_date", ISO8601Formatter.FORMATTER.format(new Date(bookmark.getTimeStamp())));
                        prop.put("display_bookmarks_"+count+"_rfc822date", HeaderFramework.formatRFC1123(new Date(bookmark.getTimeStamp())));
                        prop.put("display_bookmarks_"+count+"_public", (bookmark.getPublic() ? "1" : "0"));

                        //List Tags.
                        final Set<String> tags = bookmark.getTags();
                        int tagCount=0;
                        for (final String tag : tags) {
                            if (tag.length() > 0 && tag.charAt(0) != '/') {
                                prop.putHTML("display_bookmarks_" + count + "_tags_" + tagCount + "_tag", tag);
                                tagCount++;
                            }
                        }
                        prop.put("display_bookmarks_"+count+"_tags", tagCount);
                        prop.put("display_bookmarks_"+count+"_hash", bookmark.getUrlHash());
                        count++;
                    }
	       	}

	       	prop.putHTML("display_tag", tagName);
	       	prop.put("display_start", start);

	       	if (it.hasNext()) {
                    prop.put("display_next-page", "1");
                    prop.put("display_next-page_start", start+max_count);
                    prop.putHTML("display_next-page_tag", tagName);
                    prop.put("display_next-page_num", max_count);
	       	}
	       	if (start >= max_count) {
                    start = start-max_count;
                    if (start <0){
                        start = 0;
                    }
                    prop.put("display_prev-page", "1");
                    prop.put("display_prev-page_start", start);
                    prop.putHTML("display_prev-page_tag", tagName);
                    prop.put("display_prev-page_num", max_count);
	       	}
	       	prop.put("display_bookmarks", count);


	    	//-----------------------
	    	// create folder list
	    	//-----------------------

	       	count = 0;
	       	count = recurseFolders(BookmarkHelper.getFolderList("/", sb.bookmarksDB.getTagIterator(isAdmin)), "/", 0, true, "");
	       	prop.put("display_folderlist", count);

            BusyThread bt = sb.getThread("autosearch");
            if (bt != null) {
                prop.put("display_autosearchrunning","1");
                prop.put("display_autosearchrunning_msg", "" );
                if (post != null && post.containsKey("stopautosearch")) {
                    sb.terminateThread("autosearch", false);
                    prop.put("display_autosearchrunning_msg", "autosearch will terminate");
                    prop.put("display_autosearchrunning","0");
                }
                int jobs = bt.getJobCount();
                prop.put("display_autosearchrunning_jobcount", jobs);
                int cnt=0;
                String qstr = "";
                if (bt instanceof AutoSearch) {
                    cnt = ((AutoSearch) bt).gotresults;
                    qstr = ((AutoSearch) bt).currentQuery;
                    if (qstr == null) qstr = "---";
                }
                prop.put("display_autosearchrunning_totalcount", cnt);
                prop.put("display_autosearchrunning_query", qstr);

            } else {
                prop.put("display_autosearchrunning", "0");
                prop.put("display_autosearchrunning_msg", "");
                if (post != null && post.containsKey("startautosearch")) {
                    sb.deployThread(
                            "autosearch",
                            "Auto Search",
                            "query all peers for given search terms",
                            null,
                            new AutoSearch(Switchboard.getSwitchboard()),
                            1000);
                    prop.put("display_autosearchrunning_msg", "autsearch job started");
                }
            }
    	}
       	return prop;    // return from serverObjects respond()
    }

    private static void printTagList(final String id, final String tagName, final int comp, final int max, final boolean opt){

    	if (sb.bookmarksDB == null) {
    	    prop.put("display_"+id, 0);
    	    return;
    	}

    	int count=0;
        final Iterator<BookmarksDB.Tag> it;

        if ("".equals(tagName)) {
            it = sb.bookmarksDB.getTagIterator(isAdmin, comp, max);
        } else {
            it = sb.bookmarksDB.getTagIterator(tagName, isAdmin, comp, max);
        }

       	while(it.hasNext()){
            final Tag tag = it.next();
            if (!tag.getTagName().startsWith("/") && !"".equals(tag.getTagName())) {
                prop.putHTML("display_"+id+"_"+count+"_name", tag.getFriendlyName());
                prop.putHTML("display_"+id+"_"+count+"_tag", tag.getTagName());
                prop.put("display_"+id+"_"+count+"_num", tag.size());
                if (opt) {
                    if (tag.getFriendlyName().equals(tagName)){
                        prop.put("display_"+id+"_"+count+"_selected", " selected=\"selected\"");
                    } else {
                        prop.put("display_"+id+"_"+count+"_selected", "");
                    }
                } else {
                    // font-size is pseudo-rounded to 2 decimals
                    prop.put("display_"+id+"_"+count+"_size", Math.min(TAGCLOUD_FONTSIZE_MAX, Math.round((TAGCLOUD_FONTSIZE_MIN + Math.log(tag.size())/4f)*100.0f)/100.0f));
                }
                count++;
            }
       	}
       	prop.put("display_"+id, count);
    }

    private static int recurseFolders(final Iterator<String> it, String root, int count, final boolean next, final String prev) {

        final String fn = (next) ? it.next() : prev;

    	if("\uffff".equals(fn)) {
            int i = prev.replaceAll("[^/]","").length();
            while( i>0 ){
                prop.put("display_folderlist_"+count+"_folder", "</ul></li>");
                count++;
                i--;
            }
            return count;
    	}

    	if (fn.startsWith(("/".equals(root) ? root : root + "/"))) {
            prop.put("display_folderlist_"+count+"_folder", "<li>"+fn.replaceFirst(root+"/*","")+"<ul class=\"folder\">");
            count++;
            final Iterator<String> bit = sb.bookmarksDB.getBookmarksIterator(fn, isAdmin);

            while (bit.hasNext()) {
                Bookmark bookmark = sb.bookmarksDB.getBookmark(bit.next());
                if (bookmark == null) break;
                prop.put("display_folderlist_" + count + "_folder", "<li><a href=\"" + bookmark.getUrl() + "\" title=\"" + bookmark.getDescription() + "\">" + bookmark.getTitle() + "</a></li>");
                count++;
            }

            if (it.hasNext()) {
                count = recurseFolders(it, fn, count, true, fn);
            }

    	} else {
            prop.put("display_folderlist_"+count+"_folder", "</ul></li>");
            count++;
            root = root.replaceAll("(/.[^/]*$)", "");
            if ("".equals(root)) {
                root = "/";
            }
            count = recurseFolders(it, root, count, false, fn);
    	}
    	return count;
    }

    private static void publishNews(final String url, final String title, final String description, final String tagsString) {
    	// create a news message
        if (sb.isRobinsonMode()) return;
    	final Map<String, String> map = new HashMap<String, String>();
    	map.put("url", url.replace(',', '|'));
    	map.put("title", title.replace(',', ' '));
    	map.put("description", description.replace(',', ' '));
    	map.put("tags", tagsString.replace(',', ' '));
    	sb.peers.newsPool.publishMyNews(sb.peers.mySeed(), NewsPool.CATEGORY_BOOKMARK_ADD, map);
    }

}
