/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2022 iText Group NV
 * Authors: Balder Van Camp, Emiel Ackermann, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * 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 Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.tool.xml.pipeline.html;

import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;

import com.itextpdf.text.*;
import com.itextpdf.tool.xml.CustomContext;
import com.itextpdf.tool.xml.Experimental;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.css.apply.ListStyleTypeCssApplier;
import com.itextpdf.tool.xml.css.apply.MarginMemory;
import com.itextpdf.tool.xml.css.apply.PageSizeContainable;
import com.itextpdf.tool.xml.exceptions.NoDataException;
import com.itextpdf.tool.xml.html.CssAppliers;
import com.itextpdf.tool.xml.html.CssAppliersAware;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Header;
import com.itextpdf.tool.xml.html.Image;
import com.itextpdf.tool.xml.html.TagProcessor;
import com.itextpdf.tool.xml.html.TagProcessorFactory;
import com.itextpdf.tool.xml.html.Tags;

/**
 * The CustomContext object for the HtmlPipeline.<br />
 * Use this to configure your {@link HtmlPipeline}.
 * @author redlab_b
 *
 */
public class HtmlPipelineContext implements CustomContext, Cloneable, MarginMemory, PageSizeContainable,
		CssAppliersAware {

	/**
	 *  Key for the memory, used to store bookmark nodes
	 */
	public static final String BOOKMARK_TREE = "header.autobookmark.RootNode";
	/**
	 * Key for the memory, used in Html TagProcessing
	 */
	public static final String LAST_MARGIN_BOTTOM = "lastMarginBottom";
	private final LinkedList<StackKeeper> queue;
	private boolean acceptUnknown = true;
	private TagProcessorFactory tagFactory;
	private final List<Element> ctn = new ArrayList<Element>();
	private ImageProvider imageProvider;
	private String resourcesRootPath;
	private Rectangle pageSize = PageSize.A4;
	private Charset charset;
	private List<String> roottags = Arrays.asList(new String[] { "body", "div" });
	private LinkProvider linkprovider;
	private boolean autoBookmark = true;
	private final Map<String, Object> memory;
	private CssAppliers cssAppliers;

	/**
	 * Construct a new HtmlPipelineContext object
	 */
	public HtmlPipelineContext(CssAppliers cssAppliers) {
		this.queue = new LinkedList<StackKeeper>();
		this.memory = new HashMap<String, Object>();
        this.cssAppliers = cssAppliers;
        if (this.cssAppliers == null) {
		    this.cssAppliers = new CssAppliersImpl(new XMLWorkerFontProvider());
        }
	}
	/**
	 * @param tag the tag to find a TagProcessor for
	 * @param nameSpace the namespace.
	 * @return a TagProcessor
	 */
	protected TagProcessor resolveProcessor(final String tag, final String nameSpace) {
		TagProcessor tp = tagFactory.getProcessor(tag, nameSpace);
		if (tp instanceof CssAppliersAware) {
			((CssAppliersAware) tp).setCssAppliers(this.cssAppliers);
		}
		return tp;
	}

	/**
	 * Add a {@link StackKeeper} to the top of the stack list.
	 * @param stackKeeper the {@link StackKeeper}
	 */
	protected void addFirst(final StackKeeper stackKeeper) {
		this.queue.addFirst(stackKeeper);

	}

	/**
	 * Retrieves, but does not remove, the head (first element) of this list.
	 * @return a StackKeeper or null if there are no elements on the stack
	 */
	protected StackKeeper peek() {
		if (!this.queue.isEmpty())
			return this.queue.getFirst();
		return null;
	}

	/**
	 * @return the current content of elements.
	 */
	protected List<Element> currentContent() {
		return ctn;
	}

	/**
	 * @return if this pipelines tag processing accept unknown tags: true. False otherwise
	 */
	public boolean acceptUnknown() {
		return this.acceptUnknown;
	}

	/**
	 * @return returns true if the stack is empty
	 */
	protected boolean isEmpty() {
		return queue.isEmpty();
	}

	/**
	 * Retrieves and removes the top of the stack.
	 * @return a StackKeeper
	 * @throws NoStackException if there are no elements on the stack
	 */
	protected StackKeeper poll() throws NoStackException {
		try {
			return this.queue.removeFirst();
		} catch (NoSuchElementException e) {
			throw new NoStackException();
		}
	}
	/**
	 * @return true if auto-bookmarks should be enabled. False otherwise.
	 */
	public boolean autoBookmark() {
		return autoBookmark;
	}
	/**
	 * @return the memory
	 */
	public Map<String, Object> getMemory() {
		return memory;
	}
	/**
	 * @return the image provider or null if there is no {@link ImageProvider}.
	 *
	 */
	public ImageProvider getImageProvider() {
		return this.imageProvider;
	}

	/**
	 * Set a {@link Charset} to use.
	 * @param cSet the charset.
	 * @return this <code>HtmlPipelineContext</code>
	 */
	@Experimental
	public HtmlPipelineContext charSet(final Charset cSet) {
		this.charset = cSet;
		return this;
	}
	/**
	 * @return the {@link Charset} to use, or null if none configured.
	 */
	public Charset charSet() {
		return charset;
	}
	/**
	 * Returns a {@link Rectangle}
	 * @return the pagesize.
	 */
	public Rectangle getPageSize() {
		return this.pageSize;
	}

	/**
	 * @return a list of tags to be taken as root-tags. This matters for
	 *         margins. By default the root-tags are &lt;body&gt; and
	 *         &lt;div&gt;
	 */
	public List<String> getRootTags() {
		return roottags;
	}

	/**
	 * Returns the LinkProvider, used to prepend e.g. http://www.example.org/ to
	 * found &lt;a&gt; tags that have no absolute url.
	 *
	 * @return the LinkProvider if any.
	 */
	public LinkProvider getLinkProvider() {
		return linkprovider;
	}
	/**
	 * If no pageSize is set, the default value A4 is used.
	 * @param pageSize the pageSize to set
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setPageSize(final Rectangle pageSize) {
		this.pageSize = pageSize;
		return this;
	}

	/**
	 * Create a clone of this HtmlPipelineContext, the clone only contains the
	 * initial values, not the internal values. Beware, the state of the current
	 * Context is not copied to the clone. Only the configurational important
	 * stuff like the LinkProvider (same object), ImageProvider (new
	 * {@link AbstractImageProvider} with same ImageRootPath) ,
	 * TagProcessorFactory (same object), acceptUnknown (primitive), charset
	 * (Charset.forName to get a new charset), autobookmark (primitive) are
	 * copied.
	 */
	@Override
	public HtmlPipelineContext clone() throws CloneNotSupportedException {
        CssAppliers cloneCssApliers = this.cssAppliers.clone();
		HtmlPipelineContext newCtx = new HtmlPipelineContext(cloneCssApliers);
		if (this.imageProvider != null) {
                    newCtx.setImageProvider(imageProvider);
		}
		if (this.resourcesRootPath != null) {
			newCtx.setResourcesRootPath(resourcesRootPath);
		}
		if (null != this.charset) {
			newCtx.charSet(Charset.forName(this.charset.name()));
		}
		newCtx.setPageSize(new Rectangle(this.pageSize)).setLinkProvider(this.linkprovider)
				.setRootTags(new ArrayList<String>(this.roottags)).autoBookmark(this.autoBookmark)
				.setTagFactory(this.tagFactory).setAcceptUnknown(this.acceptUnknown).setCssApplier(cloneCssApliers);
		return newCtx;
	}


	/**
	 * Set to true to allow the HtmlPipeline to accept tags it does not find in
	 * the given {@link TagProcessorFactory}
	 *
	 * @param acceptUnknown true or false
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setAcceptUnknown(final boolean acceptUnknown) {
		this.acceptUnknown = acceptUnknown;
		return this;
	}
	/**
	 * Set the {@link TagProcessorFactory} to be used. For HTML use {@link Tags#getHtmlTagProcessorFactory()}
	 * @param tagFactory the {@link TagProcessorFactory} that should be used
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setTagFactory(final TagProcessorFactory tagFactory) {
		this.tagFactory = tagFactory;
		return this;
	}

	/**
	 * Set to true to enable the automatic creation of bookmarks on &lt;h1&gt;
	 * to &lt;h6&gt; tags. Works in conjunction with {@link Header}.
	 *
	 * @param autoBookmark true or false
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext autoBookmark(final boolean autoBookmark) {
		this.autoBookmark = autoBookmark;
		return this;
	}

	/**
	 * Set the root-tags, this matters for margins. By default these are set to
	 * &lt;body&gt; and &lt;div&gt;.
	 *
	 * @param roottags the root tags
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setRootTags(final List<String> roottags) {
		this.roottags = roottags;
		return this;
	}

	/**
	 * An ImageProvider can be provided and works in conjunction with
	 * {@link Image} and {@link ListStyleTypeCssApplier} for List Images.
	 *
	 * @param imageProvider the {@link ImageProvider} to use.
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setImageProvider(final ImageProvider imageProvider) {
		this.imageProvider = imageProvider;
		return this;
	}

	/**
	 * Set the LinkProvider to use if any.
	 *
	 * @param linkprovider the LinkProvider (@see
	 *            {@link HtmlPipelineContext#getLinkProvider()}
	 * @return this <code>HtmlPipelineContext</code>
	 */
	public HtmlPipelineContext setLinkProvider(final LinkProvider linkprovider) {
		this.linkprovider = linkprovider;
		return this;
	}
	/* (non-Javadoc)
	 * @see com.itextpdf.tool.xml.css.MarginMemory#getLastMarginBottom()
	 */
	public Float getLastMarginBottom() throws NoDataException {
		Map<String, Object> memory = getMemory();
		Object o =  memory.get(HtmlPipelineContext.LAST_MARGIN_BOTTOM);
		if (null == o) {
			throw new NoDataException();
		} else {
			return (Float) o;
		}
	}
	/* (non-Javadoc)
	 * @see com.itextpdf.tool.xml.css.MarginMemory#setLastMarginBottom(float)
	 */
	public void setLastMarginBottom(final Float lmb) {
		getMemory().put(HtmlPipelineContext.LAST_MARGIN_BOTTOM, lmb);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.itextpdf.tool.xml.html.CssAppliersAware#setCssAppliers(com.itextpdf.tool.xml.html.CssAppliers)
	 */
	public void setCssAppliers(final CssAppliers cssAppliers) {
		this.cssAppliers = cssAppliers;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see com.itextpdf.tool.xml.html.CssAppliersAware#getCssAppliers()
	 */
	public CssAppliers getCssAppliers() {
		return cssAppliers;
	}

	/**
	 * Fluent variant of {@link #setCssAppliers(CssAppliers)}
	 *
	 * @param cssAppliers the cssAppliers
	 * @return this
	 */
	public HtmlPipelineContext setCssApplier(final CssAppliers cssAppliers) {
		this.cssAppliers = cssAppliers;
		return this;
	}

	public String getResourcesRootPath() {
		return resourcesRootPath;
	}

	public void setResourcesRootPath(String resourcesRootPath) {
		this.resourcesRootPath = resourcesRootPath;
	}
}
