/* This code is part of Freenet. It is distributed under the GNU General
 * Public License, version 2 (or at your option any later version). See
 * http://www.gnu.org/ for further details of the GPL. */
package freenet.clients.http.bookmark;

import java.net.MalformedURLException;

import freenet.client.async.ClientGetter;
import freenet.keys.FreenetURI;
import freenet.keys.USK;
import freenet.l10n.NodeL10n;
import freenet.node.FSParseException;
import freenet.node.NodeClientCore;
import freenet.node.useralerts.AbstractUserAlert;
import freenet.node.useralerts.UserAlert;
import freenet.node.useralerts.UserAlertManager;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;

public class BookmarkItem extends Bookmark {
    public static final String NAME = "Bookmark";
    private final BookmarkManager manager;
    private FreenetURI key;
    private boolean updated;
    private boolean hasAnActivelink = false;
    private final BookmarkUpdatedUserAlert alert;
    private final UserAlertManager alerts;
    protected String desc;
    protected String shortDescription;

    public BookmarkItem(FreenetURI k, String n, String d, String s, boolean hasAnActivelink,
            BookmarkManager bm, UserAlertManager uam) throws MalformedURLException {
        this.key = k;
        this.name = n;
        this.desc = d;
        this.shortDescription = s;
        this.hasAnActivelink = hasAnActivelink;
        this.manager = bm;
        this.alerts = uam;
        alert = new BookmarkUpdatedUserAlert();
        assert(name != null);
        assert(key != null);
    }

    /** Please call {@link #registerUserAlert()} afterwards. */
    public BookmarkItem(SimpleFieldSet sfs, BookmarkManager bm, UserAlertManager uam)
            throws FSParseException, MalformedURLException {
        this.name = sfs.get("Name");
        if(name == null || name.isEmpty()) name = l10n("unnamedBookmark");
        this.desc = sfs.get("Description");
        if(desc == null) desc = "";
        this.shortDescription = sfs.get("ShortDescription");
        if(shortDescription == null) shortDescription = "";
        this.hasAnActivelink = sfs.getBoolean("hasAnActivelink");
        // "Updated" was added in 2016-08-19, so we must assume it doesn't exist in previously saved
        // bookmark databases and provide a default to prevent getBoolean() from throwing.
        // TODO: Code quality: Remove default after a year or so.
        this.updated = sfs.getBoolean("Updated", false);
        this.key = new FreenetURI(sfs.get("URI"));
        this.manager = bm;
        this.alerts = uam;
        this.alert = new BookmarkUpdatedUserAlert();
    }

	private static volatile boolean logMINOR;

	static {
		Logger.registerClass(ClientGetter.class);
	}

    private class BookmarkUpdatedUserAlert extends AbstractUserAlert {

        public BookmarkUpdatedUserAlert() {
            super(true, null, null, null, null, UserAlert.MINOR, false, null, true, null);
        }

        @Override
		public String getTitle() {
            return l10n("bookmarkUpdatedTitle", "name", name);
        }

        @Override
		public String getText() {
            return l10n("bookmarkUpdated", new String[]{"name", "edition"},
                    new String[]{name, Long.toString(key.getSuggestedEdition())});
        }

        @Override
		public HTMLNode getHTMLText() {
            HTMLNode n = new HTMLNode("div");
            NodeL10n.getBase().addL10nSubstitution(n, "BookmarkItem.bookmarkUpdatedWithLink", new String[]{"link", "name", "edition"},
            		new HTMLNode[] { HTMLNode.link("/"+key), HTMLNode.text(name), HTMLNode.text(key.getSuggestedEdition()) });
            return n;
        }

        @Override
		public boolean isValid() {
            synchronized (BookmarkItem.this) {
                return updated;
            }
        }

        @Override
		public void isValid(boolean validity) {
            if (validity) {
                return;
            }
            disableBookmark();
        }

        @Override
		public String dismissButtonText() {
            return l10n("deleteBookmarkUpdateNotification");
        }

        @Override
		public void onDismiss() {
            disableBookmark();
            manager.storeBookmarks();
        }

		@Override
		public String getShortText() {
			return l10n("bookmarkUpdatedShort", "name", name);
		}

		@Override
		public boolean isEventNotification() {
			return true;
		}
    }

    private synchronized void disableBookmark() {
        updated = false;
        alerts.unregister(alert);
    }

    private String l10n(String key) {
        return NodeL10n.getBase().getString("BookmarkItem." + key);
    }

    private String l10n(String key, String pattern, String value) {
        return NodeL10n.getBase().getString("BookmarkItem." + key, new String[]{pattern}, new String[]{value});
    }

    private String l10n(String key, String[] patterns, String[] values) {
        return NodeL10n.getBase().getString("BookmarkItem." + key, patterns, values);
    }

    private synchronized void enableBookmark() {
        if (updated) {
            return;
        }
        assert(key.isUSK());
        updated = true;
        alerts.register(alert);
    }

    /**
     * If this bookmark is marked as updated, registers a {@link UserAlert} which notifies the user.
     * You usually only need to call this function after having loaded a bookmark from disk using
     * {@link #BookmarkItem(SimpleFieldSet, BookmarkManager, UserAlertManager)}. */
    synchronized void registerUserAlert() {
        if (key.isUSK() && updated)
            alerts.register(alert);
    }

    public UserAlert getUserAlert() {
        return alert;
    }

    public String getKey() {
        return key.toString();
    }

    public synchronized FreenetURI getURI() {
        return key;
    }

    public synchronized void update(FreenetURI uri, boolean hasAnActivelink, String description, String shortDescription) {
        this.key = uri;
        this.desc = description;
        this.shortDescription = shortDescription;
        this.hasAnActivelink = hasAnActivelink;
        if(!key.isUSK())
        	disableBookmark();
    }

    public synchronized String getKeyType() {
        return key.getKeyType();
    }

    @Override
	public String getName() {
        return name;
    }

    @Override
	public String toString() {
        return this.name + "###" + (this.desc != null ? this.desc : "") + "###" + this.hasAnActivelink + "###" + this.key.toString();
    }

    /** @return True if we updated the edition */
    public synchronized boolean setEdition(long ed, NodeClientCore node) {
        if (key.getSuggestedEdition() >= ed) {
        	if(logMINOR) Logger.minor(this, "Edition "+ed+" is too old, not updating "+key);
            return false;
        }
        key = key.setSuggestedEdition(ed);
        enableBookmark();
        return true;
    }

    public USK getUSK() throws MalformedURLException {
        return USK.create(key);
    }

	@Override
	public int hashCode() {
		int hash = super.hashCode();
		hash = 31 * hash + this.key.setSuggestedEdition(0).hashCode();
		hash = 31 * hash + (this.hasAnActivelink ? 1 : 0);
		hash = 31 * hash + (this.desc != null ? this.desc.hashCode() : 0);
		return hash;
	}

    @Override
	public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (o instanceof BookmarkItem) {
            BookmarkItem b = (BookmarkItem) o;
            if (!super.equals(o)) {
                return false;
            }
            if (!b.key.equals(key)) {
				if ("USK".equals(b.key.getKeyType())) {
                    if (!b.key.setSuggestedEdition(key.getSuggestedEdition()).equals(key)) {
                        return false;
                    }
                } else {
                    return false;
                }
            }
            if (b.alerts != alerts) {
                return false;
            } // Belongs to a different node???
            if (b.hasAnActivelink != hasAnActivelink) {
                return false;
            }
			if (b.desc.equals(desc))
				return true;
			if (b.desc == null || desc == null)
				return false;
		if(!b.desc.equals(desc)) {
	                return false;
		}
            return true;
        } else {
            return false;
        }
    }

    public boolean hasUpdated() {
        return updated;
    }

    public void clearUpdated() {
        this.updated = false;
    }

    public boolean hasAnActivelink() {
        return hasAnActivelink;
    }
    
    public String getDescription() {
    	if(desc == null) return "";
		if(desc.toLowerCase().startsWith("l10n:"))
			return NodeL10n.getBase().getString("Bookmarks.Defaults.Description."+desc.substring("l10n:".length()));
        return desc;
    }
    
    public String getShortDescription() {
    	if(shortDescription == null) return "";
		if(shortDescription.toLowerCase().startsWith("l10n:"))
			return NodeL10n.getBase().getString("Bookmarks.Defaults.ShortDescription."+shortDescription.substring("l10n:".length()));
        return shortDescription;
    }
    
    @Override
	public SimpleFieldSet getSimpleFieldSet() {
	SimpleFieldSet sfs = new SimpleFieldSet(true);
	sfs.putSingle("Name", name);
	sfs.putSingle("Description", desc);
	sfs.putSingle("ShortDescription", shortDescription);
	sfs.put("hasAnActivelink", hasAnActivelink);
	sfs.put("Updated", updated);
	sfs.putSingle("URI", key.toString());
	return sfs;
    }
}
