// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.lagarto.form;

import jodd.lagarto.Tag;
import jodd.lagarto.TagType;
import jodd.lagarto.TagWriter;
import jodd.mutable.MutableInteger;
import jodd.util.StringUtil;

import java.util.HashMap;
import java.util.Map;

/**
 * Form processor. Invokes {@link jodd.lagarto.form.FormFieldResolver} on form fields.
 */
public class FormProcessorVisitor extends TagWriter {

	private static final char[] INPUT = new char[] {'i', 'n', 'p', 'u', 't'};
	private static final char[] TYPE = new char[] {'t', 'y', 'p', 'e'};
	private static final char[] SELECT = new char[] {'s', 'e', 'l', 'e', 'c', 't'};
	private static final char[] OPTION = new char[] {'o', 'p', 't', 'i', 'o', 'n'};
	private static final char[] TEXTAREA = new char[] {'t', 'e', 'x', 't', 'a', 'r', 'e', 'a'};

	private static final String VALUE = "value";
	private static final String NAME = "name";
	private static final String TEXT = "text";
	private static final String HIDDEN = "hidden";
	private static final String IMAGE = "image";
	private static final String PASSWORD = "password";
	private static final String CHECKBOX = "checkbox";
	private static final String TRUE = "true";
	private static final String CHECKED = "checked";
	private static final String RADIO = "radio";
	private static final String SELECTED = "selected";

	private final FormFieldResolver resolver;

	public FormProcessorVisitor(Appendable appendable, FormFieldResolver resolver) {
		super(appendable);
		this.resolver = resolver;
	}

	@Override
	public void tag(Tag tag) {
		if (tag.getType().isStartingTag()) {
			if (tag.matchTagName(INPUT)) {
				processInputStartTag(tag);
				super.tag(tag);
				return;
			}
			if (inSelect && tag.matchTagName(OPTION)) {
				processOptionOpenTag(tag);
				super.tag(tag);
				return;
			}
		}

		if (tag.getType() == TagType.START) {
			if (tag.matchTagName(TEXTAREA)) {
				processTextareaStartTag(tag);
			}
			else if (tag.matchTagName(SELECT)) {
				processSelectOpenTag(tag);
			}
		}
		else if (tag.getType() == TagType.END) {
			if (inTextArea && tag.matchTagName(TEXTAREA)) {
				processTextareaEndTag();
			}
			else if (inSelect && tag.matchTagName(SELECT)) {
				processSelectEndTag();
			}
		}

		super.tag(tag);
	}

	@Override
	public void text(CharSequence text) {
		if (inTextArea) {
			return;
		}
		super.text(text);
	}

	// ---------------------------------------------------------------- input

	private void processInputStartTag(Tag tag) {
		// INPUT
		CharSequence tagType = tag.getAttributeValue(TYPE);
		if (tagType == null) {
			return;
		}
		CharSequence nameSequence = tag.getAttributeValue(NAME);
		if (nameSequence == null) {
			return;
		}

		String name = nameSequence.toString();

		Object valueObject = resolver.value(name);
		if (valueObject == null) {
			return;
		}

		String tagTypeName = tagType.toString().toLowerCase();

		if (
				tagTypeName.equals(TEXT) ||
				tagTypeName.equals(HIDDEN) ||
				tagTypeName.equals(IMAGE) ||
				tagTypeName.equals(PASSWORD)) {

			String value = valueToString(name, valueObject);

			if (value == null) {
				return;
			}

			tag.setAttribute(VALUE, value);
		}
		else if (tagTypeName.equals(CHECKBOX)) {
			CharSequence tagValue = tag.getAttributeValue(VALUE);
			if (tagValue == null) {
				tagValue = TRUE;
			}
			tagValue = tagValue.toString();

			if (valueObject.getClass().isArray()) {
				// checkbox group
				String[] vs = StringUtil.toStringArray(valueObject);
				for (String vsk : vs) {
					if ((vsk != null) && (vsk.equals(tagValue))) {
						tag.setAttribute(CHECKED, null);
					}
				}
			} else if (tagValue.equals(valueObject.toString())) {
				tag.setAttribute(CHECKED, null);
			}
		}
		else if (tagType.equals(RADIO)) {
			CharSequence tagValue = tag.getAttributeValue(VALUE);
			if (tagValue != null) {
				tagValue = tagValue.toString();
				if (tagValue.equals(valueObject.toString())) {
					tag.setAttribute(CHECKED, null);
				}
			}
		}
	}

	// ---------------------------------------------------------------- convert values to string

	protected Map<String, MutableInteger> valueNameIndexes;

	/**
	 * Converts value to a string.
	 */
	protected String valueToString(String name, Object valueObject) {
		if (!valueObject.getClass().isArray()) {
			return valueObject.toString();
		}

		// array
		String[] array = (String[]) valueObject;

		if (valueNameIndexes == null) {
			valueNameIndexes = new HashMap<>();
		}

		MutableInteger index = valueNameIndexes.get(name);
		if (index == null) {
			index = new MutableInteger(0);
			valueNameIndexes.put(name, index);
		}

		if (index.value >= array.length) {
			return null;
		}

		String result = array[index.value];

		index.value++;

		return result;
	}

	// ---------------------------------------------------------------- select

	private boolean inSelect;
	private String currentSelectName;

	private void processSelectOpenTag(Tag tag) {
		CharSequence name = tag.getAttributeValue(NAME);

		if (name == null) {
			return;
		}

		currentSelectName = name.toString();
		inSelect = true;
	}

	private void processSelectEndTag() {
		inSelect = false;
		currentSelectName = null;
	}

	private void processOptionOpenTag(Tag tag) {
		CharSequence tagValue = tag.getAttributeValue(VALUE);
		if (tagValue == null) {
			return;
		}

		Object vals = resolver.value(currentSelectName);
		if (vals == null) {
			return;
		}
		tagValue = tagValue.toString();

		if (vals.getClass().isArray()) {
			String[] vs = StringUtil.toStringArray(vals);
			for (String vsk : vs) {
				if ((vsk != null) && (vsk.equals(tagValue))) {
					tag.setAttribute(SELECTED, null);
				}
			}
		} else {
			String value = StringUtil.toString(vals);
			if (value.equals(tagValue)) {
				tag.setAttribute(SELECTED, null);
			}
		}
	}

	// ---------------------------------------------------------------- textarea

	private String textAreaValue;
	private boolean inTextArea;

	private void processTextareaStartTag(Tag tag) {
		inTextArea = true;

		CharSequence name = tag.getAttributeValue(NAME);
		if (name == null) {
			return;
		}
		Object valueObject = resolver.value(name.toString());
		if (valueObject != null) {
			textAreaValue = valueObject.toString();
		}
	}

	private void processTextareaEndTag() {
		inTextArea = false;
		if (textAreaValue == null) {
			return;
		}
		super.text(textAreaValue);
		textAreaValue = null;
	}

}