//
// https://www.ramecera.it/myscrewdriver.html
// e-mail: screwdriver@ramecera.it
//
/* This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html */
package it.ramecera.screwdriver;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import it.ramecera.salad.everyday.Candies;
import it.ramecera.salad.everyday.HandleDate;
import it.ramecera.salad.everyday.Mix;
import it.ramecera.salad.everyday.Strings;
import it.ramecera.salad.everyday.ExactException;
import it.ramecera.screwdriver.outfits.JspKit;
import it.ramecera.screwdriver.outfits.JspBeanElement;

/**
 * <p>
 * The <b>wagon</b> object indicates a markup with its data.
 * </p>
 * <p>
 * Company: Ramecera™<br>
 * Creation date: 2020-04-09<br>
 * Last updated: 2020-04-09
 * </p>
 * <p>
 * Modifications:<br>
 * </p>
 * 
 * @author M. Maglioni */
final public class Wagon {

	// PUBLIC PROPERTIES

	/** the stub */
	public String stub;
	/** the main data */
	public Object substance;
	/** the text */
	public String text;
	/** the N. in the corresponding text file */
	public int rigaJ;
	/** the N. placed in the train list on the corresponding text
	 * file */
	public int positionJ;
	/** amount of words of the substance */
	public int wordQ;
	/** technical evaluation of the text (if it is a SQL query of type
	 * select, how many columns there are; 1 = a single column of
	 * select; 2 = more columns of select) */
	public int valutazioneColonne;
	/** if this segment is to be considered non-existent, remember that
	 * the default is false; a train is called a "follower" when it is
	 * the logical continuation of a previous train; it can be a
	 * continuation line of a text, or it can be a regular train that
	 * adds information to a previous train (example: formt+ is a
	 * follower of jsp+) */
	public boolean follower;

	/** ordinary logger */
	private static final Logger logger = Logger.getLogger(Wagon.class);

	/** default constructor */
	public Wagon() {

	}

	/**
	 * <p>
	 * Creation date: 2020-04-29<br>
	 * Last updated: 2020-06-27<br>
	 * 
	 * @author Massimo Maglioni
	 *         <p>
	 *         Description: This constructor is used to completely
	 *         define each markup: a line of text preceded by a label.
	 *         </p>
	 * 
	 * @param markupE the list of markups
	 * @param stub the name of the markup
	 * @param substance object containing the data, i.e. a date, string
	 *        or number
	 * @param rigaJ the line number on the
	 * @param hole the position that will occupy the strap of this
	 *        segment in the list of trains; it is obtained from the
	 *        account of valid trains obtained with the variable zQ
	 * @param trainW the previous train object, null if this train is
	 *        the first; is the previous train in a logical sense,
	 *        because there may be follower trains in the middle
	 * @param position of the markup position in the .ele file,
	 *        corrected for the presence of .eli files with the function
	 *        of include
	 * @throws ExactException the new segment is incorrect */
	public Wagon(HashMap<String, Markup> markupE, final String stub,
			Object substance, final int rigaJ, final int hole,
			Train trainW, final int position)

			throws ExactException {

		this.stub = stub;
		ExactException ne = new ExactException();
		/* I memorize the type of markup */
		int p = markupE.get(stub).p;
		this.rigaJ = rigaJ;
		this.substance = substance;
		/* at this moment the substance can only be a string; only later
		 * will we qualify it as a date or integer or booelan */
		if (!this.substance.getClass().getName().equals(
				"java.lang.String")) {
			this.wordQ = 0;
			ne.datum = "Attempt to construct a segment by supplying"
					+ " a substance that is not a string.";
			throw ne;
		} else if (this.substance.equals("")) {
			this.wordQ = 0;
		} else {
			this.wordQ = ((String) this.substance).split(" ").length;
		}
		String x = ((String) substance);
		logger.trace("stub=" + stub + ", x=" + x);
		String message = null;
		switch (p) {
		/* The follower segment is a continuation segment of the text which
		 * is called follower because after its use it loses all
		 * reference */
		case 0: /* a free text; by definition the segment is follower */
			if (trainW == null) {
				message = "An \"ele\" file must begin with a regular"
						+ " markup, not with a text: I have found "
						+ Mix.dress_A(x) + ".";
				ne.code = 1600;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			}
			// SHE'S DEAD
			follower = true;
			break;
		case 1: // a date
			x = Mix.compactingSpaces_A(x);
			if (x.length() == 0) {
				message = "There should be a date at the markup " + Mix
						.dress_A(markupE.get(stub).stub) + " at line "
						+ position
						+ ", on the contrary there is nothing.";
				ne.code = 1601;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			} else if (!HandleDate.eDataValida(x, "d-M-yyyy")
					&& !HandleDate.eDataValida(x, "d/M/yyyy")
					&& HandleDate.eLongValidDate(x) == null) {
				message = "There should be a date at the markup " + Mix
						.dress_A(markupE.get(stub).stub) + " at line "
						+ position + ", on the contrary I have found "
						+ Mix.dress_A(x);
				ne.code = 1602;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			} else if (HandleDate.eDataValida(x, "d-M-yyyy")) {
				this.substance = HandleDate.pacco(x);
			} else if (HandleDate.eDataValida(x, "d/M/yyyy")) {
				this.substance = HandleDate.pacco(x);
			} else
				this.substance = HandleDate.eLongValidDate(x);
			break;
		case 7: // a CSS color
			x = Mix.compactingSpaces_A(x).toLowerCase();
			if (x.length() == 0) {
				message = "There should be a CSS color at the markup "
						+ Mix.dress_A(markupE.get(stub).stub)
						+ " at line " + position
						+ ", on the contrary there is nothing.";
				ne.code = 1603;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			} else if (!Mix.isCssColor(x) & !x.matches(
					"\\x23[0123456789abcdef]{6}") & !x.matches(
							"rgb\\x28\\d+\\x2c\\x20*\\d+\\x2c\\x20*\\d+\\x29")) {
				message = "There should be a CSS color at the markup "
						+ Mix.dress_A(markupE.get(stub).stub)
						+ " at line " + position
						+ ", on the contrary I have found " + Mix
								.dress_A(x);
				ne.code = 1604;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else {
				this.text = x.trim();
				this.substance = x.trim();
			}
			break;
		case 2: // an unsigned positive integer, mandatory
			x = Mix.compactingSpaces_A(x);
			if (x.length() == 0) {
				message = "There should be a CSS color at the markup"
						+ markupE.get(stub).stub + "» at line "
						+ position
						+ ", on the contrary there is nothing.";
				ne.code = 1605;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (!Pattern.matches("\\d+", x)) {
				message = "There should be a number at the markup «"
						+ markupE.get(stub).stub + " at line "
						+ position + ", on the contrary I have found "
						+ Mix.dress_A(x);
				ne.code = 1606;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			}
			this.text = x;
			this.substance = Long.valueOf(x);
			break;
		case 15: // an integer with or without sign, mandatory
			x = Mix.compactingSpaces_A(x);
			if (x.length() == 0) {
				message = "There should be a number at the markup "
						+ Mix.dress_A(markupE.get(stub).stub)
						+ " at line " + position
						+ ", on the contrary there is nothing.";
				ne.code = 1607;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (!Pattern.matches("[+-]?\\d+", x)) {
				message = "There should be a number at the markup "
						+ Mix.dress_A(markupE.get(stub).stub)
						+ " at line " + position
						+ ", on the contrary I have found " + Mix
								.dress_A(x);
				ne.code = 1608;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			}
			this.text = x;
			this.substance = Long.valueOf(x);
			break;
		case 10: /* a string that refers to an include */
		case 6: // a string, optional
			x = x.replaceAll("\\s", " ");
			x = Mix.compactingSpaces_A(x);
			this.text = x;
			if (x.length() == 0) {
				break;
			}
		case 20: // a string
			if (p == 20) {
				this.substance = "";
			}
		case 3: // a string
			x = Strings.compact_A(x);
			if (x.length() == 0) {
				message = "After the markup «" + markupE.get(stub).stub
						+ "» at line " + rigaJ
						+ " there must be anything, "
						+ "on the contrary there is nothing.";
				ne.code = 1609;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			}
			if (stub.equals("sdefauto")) {
				x = x.toLowerCase();
				switch (x) {
				case "no":
					this.text = "no";
					break;
				case "byname":
					this.text = "byName";
					break;
				case "bytype":
					this.text = "byType";
					break;
				case "autodetect":
					this.text = "autodetect";
					break;
				case "constructor":
					this.text = "constructor";
					break;
				default:
					ne.code = 1610;
					ne.datum = "The value " + Mix.dress_A(x)
							+ " for the markup " + Mix.dress_A(markupE
									.get(stub).stub) + " at line "
							+ position + " was not expected.";
					throw ne;
				}
			} else if (stub.startsWith("form") & markupE.get(
					"jsp").recurrenceE.size() == 0) {
				message = "The markup " + Mix.dress_A(stub)
						+ " at line " + position
						+ " must be preceded (even if not immediately) "
						+ "by the markup «jsp».";
				ne.code = 1612;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (stub.equals("formt")) {
				this.text = x;
				follower = true;
			} else if (stub.equals("forma")) {
				this.text = x;
				follower = true;
			} else if (stub.equals("formc")) {
				this.text = x;
				follower = true;
			} else if (stub.equals("formn")) {
				this.text = x;
				follower = true;
			} else if (stub.equals("formr")) {
				this.text = x;
				follower = true;
			} else if (stub.equals("legend")) {
				((JspKit) trainW.locomotor.substance).legend = x;
			} else if (markupE.get(stub).implyE != null && markupE.get(
					stub).implyE.length == 1 && markupE.get(
							stub).implyE[0].isWagon) {
				this.text = x;
				follower = true;
			} else {
				this.text = x;
			}
			break;
		case 21: /* a string that refers to an include */
		case 23: /* a language code */
		case 19:
			x = Mix.compactingSpaces_A(x);
			int j = 0;
			String[] eW = markupE.get(stub).expectedValueE;
			for (; j < eW.length; j++) {
				if (eW[j].trim().equalsIgnoreCase(x)) {
					/* I correct a possible inaccuracy on upper / lower
					 * case */
					x = eW[j];
					break;
				}
			}
			if (j == eW.length & x.isEmpty()) {
				if (p == 23) {
					message = "For the markup «" + markupE.get(stub)
							+ "» the expected values are the language codes ISO 639-1.";
				} else {
					message = "For the markup «" + markupE.get(
							stub).stub + "» the expected values are: ";
					for (String u : markupE.get(stub).expectedValueE) {
						message += u + ", ";
					}
					message += "but no value has been declared.";
				}
				ne.code = 1626;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (j == eW.length) {
				if (p == 23) {
					message = "For the markup " + Mix.dress_A(markupE
							.get(stub).stub)
							+ " the expected values are according to "
							+ "language code ISO 639-1, ";
				} else {
					message = "For the markup «" + stub
							+ "» the expected values are: ";
					for (String u : markupE.get(stub).expectedValueE) {
						message += u + ", ";
					}
				}
				message += "but I have found " + Mix.dress_A(x) + ".";
				ne.code = 1628;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			}
			if (p == 23) {
				this.text = x.toLowerCase();
			} else {
				this.text = x;
			}
			if (p != 21) {
				this.substance = Integer.valueOf(j);
			}
			break;
		case 11: /* a string that refers to an include */
		case 5: // a version of software
			x = Mix.compactingSpaces_A(x);
			message = "The markup " + Mix.dress_A(stub) + " at line "
					+ rigaJ + " presents a value " + x
					+ " which on the contrary should be a version"
					+ " of software.";
			this.substance = Mix.absoluteVersion(x, message, 1627);
			this.text = x;
		case 16: // like 12 but optional
			x = x.replaceAll("\\s", " ");
			x = Mix.compactingSpaces_A(x);
			this.text = x;
			if (x.length() == 0) {
				break;
			}
		case 8: // like 12, and automatically converted to lowercase
		case 12: // a one-word string
			x = Mix.compactingSpaces_A(x);
			if (x.length() == 0 | x.indexOf(32) > -1) {
				message = "After the markup " + Mix.dress_A(stub)
						+ " at line " + position
						+ " there must be something, and it must be one word.";
				ne.code = 1613;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (stub.equals("sinterce")) {
				/* make sure the initial is capitalized, it's a classse
				 * java! */
				this.text = x.substring(0, 1).toUpperCase() + x
						.substring(1);
			} else if (stub.equals("bmenu")) {
				this.text = x.toLowerCase();
				follower = true;
			} else if (stub.equals("requmap") | stub.equals("burl")) {
				if (x.startsWith(Candies.SLASH) & !x.equals(
						Candies.SLASH)) {
					message = "At line " + position + ", " + stub + "+"
							+ x + " path starts with a slash,"
							+ " it will be automatically removed.";
					logger.debug(message);
					this.substance = x.substring(1);
					this.text = x;
				} else {
					this.text = x;
				}
			} else if (p == 8) {
				this.text = x.toLowerCase();
			} else {
				this.text = x;
			}
			break;
		case 14: /* a string of a path plus file name, not more than 1
				  * slash, but optional */
			if ((stub.equals("edefn") | stub.equals("sieco")) & markupE
					.get("jsp").recurrenceE.size() == 0) {
				message = "The markup " + Mix.dress_A(stub)
						+ " at line " + position
						+ " must be preceded by the markup «jsp».";
				ne.code = 1616;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			}
			x = x.replaceAll("\\t", " ");
			x = Mix.compactingSpaces_A(x);
			this.text = x;
			if (x.length() == 0) {
				x = (markupE.get("jsp").recurrenceE.get(
						0)).locomotor.text;
			}

		case 13: /* a string of a path plus file name, not more than 1
				  * slash */
			x = Mix.compactingSpaces_A(x);
			x = x.replaceAll("\\x5c", "/"); // x5c is the backslash
			if (Mix.stringheQ(x, "/") > 1 | Mix.stringheQ(x, ".") < 1) {
				message = "After the markup " + Mix.dress_A(markupE.get(
						stub).stub) + " at line " + position
						+ " there is an irregular path; it needs"
						+ " one directory without initial slash"
						+ " and a file with its extension.";
				ne.code = 1617;
				ne.datum = message;
				ne.className = this.getClass().getName();
				ne.line = rigaJ;
				throw ne;
			} else {
				this.text = x;
				if (x.indexOf(47) == -1) { // ASCII 47 is the bar
					x = "/" + x;
				}
				// hexadecimal 2f is the slash
				this.substance = Mix.analizzaPath(x);
			}
			if (stub.equals("jsp")) {
				String[] s = Mix.analizzaPath(x);
				this.substance = new JspKit(s[0], s[1], s[2], false,
						new ArrayList<JspBeanElement>(), "",
						new ArrayList<Wagon>(), positionJ, null, 0);
			}
			break;

		case 9: /* a string that refers to an include */
		case 4: // empty
			/* Blank means markup without any parameters. To express a
			 * Boolean value, true / false is not used, but two distinct
			 * markups are used */
			if (x.length() > 0) {
				message = "After the markup " + Mix.dress_A(markupE.get(
						stub).stub) + " at line " + position
						+ " there must be nothing, on the contrary"
						+ " I have found " + Mix.dress_A(x) + ".";
				ne.code = 1618;
				ne.method = "Wagon constructor";
				ne.datum = message;
				ne.line = rigaJ;
				ne.className = this.getClass().getName();
				throw ne;
			}
			if (markupE.get(stub).implyE != null) {
				for (int n = 0; n < markupE.get(
						stub).implyE.length; n++) {
					if (!markupE.get(stub).implyE[n].value.equals("")) {
						message = "The markup " + Mix.dress_A(stub)
								+ " at line " + rigaJ
								+ " presents as supposed the markup "
								+ Mix.dress_A(markupE.get(
										stub).implyE[n].stub
										+ Markup.PIU + markupE.get(
												stub).implyE[n].value)
								+ " which should be a software version.";
						this.substance = Mix.absoluteVersion(markupE
								.get(stub).implyE[n].value, message,
								1625);
					}
				}
			}
			if (stub.equals("pragma")) {
				follower = true;
			} else if (stub.equals("blank")) {
				((JspKit) trainW.locomotor.substance).blank = true;
			}
			break;

		case 17: /* a mandatory string, consisting of substrings
				  * separated by commas */
		case 24: /* a mandatory string, consisting of words separated by
				  * commas */
			x = Strings.compact_A(x);
			if (x.length() == 0) {
				message = "After the markup «" + markupE.get(stub).stub
						+ "» at line " + rigaJ
						+ " there must be anything, on the contrary"
						+ " there is nothing.";
				ne.code = 1619;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			}
			String[] z = x.split("\\x2c"); // x2c is the comma
			String[][] ss17 = new String[2][z.length];
			for (int i = 0; i < z.length; i++) {
				ss17[0][i] = z[i].trim();
				if (ss17[0][i].isEmpty()) {
					message = "After the markup " + markupE.get(
							stub).stub + " at line " + rigaJ
							+ " there must be a list, but element n. "
							+ (i + 1) + " is empty.";
					ne.code = 1620;
					ne.datum = message;
					ne.className = this.getClass().getName();
					throw ne;
				} else if (ss17[0][i].indexOf(32) > -1 & p == 24) {
					message = "At the markup " + markupE.get(stub).stub
							+ " at line " + rigaJ
							+ " there must be a list of words, but element n. "
							+ (i + 1) + " has more than one word.";
					ne.code = 1620;
					ne.datum = message;
					ne.className = this.getClass().getName();
					throw ne;
				}
			}
			this.substance = ss17;
			break;

		case 22: /* an optional string, consisting of substrings
				  * separated by commas */
			x = x.replaceAll("\\s", " ");
			x = Mix.compactingSpaces_A(x);
			if (x.length() > 0) {
				String[] ss22 = x.split("\\x2c"); // x2c is the comma
				for (int i = 0; i < ss22.length; i++) {
					ss22[i] = ss22[i].trim();
				}
				this.substance = ss22;
				this.text = x;
			}
			break;

		case 18: /* a mandatory string, consisting of a select SQL */
			x = x.trim();
			if (x.length() == 0) {
				message = "After the markup «" + markupE.get(stub).stub
						+ "» at line " + rigaJ
						+ " there must be anything, on the contrary"
						+ " there is nothing.";
				ne.code = 1622;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else if (!x.toLowerCase().startsWith("select ")) {
				message = "The markup " + Mix.dress_A(markupE.get(
						stub).stub) + " at line " + rigaJ
						+ " must start with the word «select».";
				ne.code = 1623;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			} else {
				String w = x;
				w = w.replaceAll("concat([^)]+)", "(dummy");
				int comma = w.indexOf(44); // ASCII 44 is comma
				int sqlFrom = w.indexOf(" from ");
				if (comma == -1 | comma > sqlFrom) {
					this.valutazioneColonne = 1;
				} else {
					this.valutazioneColonne = 2;
				}
				this.text = x;
			}
			break;
		default:
			ne.code = 1650;
			ne.datum = "The value " + p + " for the markup " + Mix
					.dress_A(stub) + " at line " + position
					+ " was not expected.";
			throw ne;
		}
		if (!follower) {
			/* identical non-repeat check */
			if (markupE.get(stub).presenza == 4 | markupE.get(
					stub).presenza == 5) {
				int i = 0;
				for (; i < markupE.get(stub).recurrenceE.size(); i++) {
					if (markupE.get(stub).recurrenceE.get(
							i).locomotor.text.equalsIgnoreCase(x)) {
						break;
					}
				}
				if (i < markupE.get(stub).recurrenceE.size()) {
					message = "The markup " + Mix.dress_A(stub)
							+ " at line " + position
							+ " is a duplicate.";
					ne.code = 1621;
					ne.datum = message;
					ne.className = this.getClass().getName();
					throw ne;
				}
			}
		}
		if (markupE.get(stub).precedingE != null) {
			int i = 0;
			for (; i < markupE.get(stub).precedingE.length; i++) {
				if (markupE.get(stub).precedingE[i].equals(
						trainW.locomotor.stub)) {
					break;
				}
			}
			if (i == markupE.get(stub).precedingE.length) {
				message = "The markup " + Mix.dress_A(stub)
						+ " at line " + position
						+ " has as previous train " + Mix.dress_A(
								trainW.locomotor.stub)
						+ ", is incorrect";
				ne.code = 1624;
				ne.datum = message;
				ne.className = this.getClass().getName();
				throw ne;
			}
		}
	}

	// PUBLIC METHODS

	/**
	 * <p>
	 * Classic clone method.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09<br>
	 * </p>
	 * 
	 * @return the resulting string */
	final public Wagon clone() {
		Wagon f = new Wagon();
		f.follower = follower;
		f.wordQ = wordQ;
		f.rigaJ = rigaJ;
		f.positionJ = positionJ;
		f.substance = substance;
		f.stub = stub;
		f.text = text;
		return f;
	}

	/**
	 * <p>
	 * Display of a wagon.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * Last updated: 2020-04-09
	 * </p>
	 * 
	 * @param i wagon index
	 * @param mE the list of markups
	 * @return the string resultant */
	final public String toString(final int i,
			HashMap<String, Markup> mE) {
		String s = "";
		if (i > 0) {
			s += "wagon " + i + ") ";
		}
		s += "[" + mE.get(stub).stub + "], line = " + rigaJ
				+ "; place = " + positionJ;
		if (text != null) {
			s += "; /text/-" + text;
		}
		s += "; className: " + substance.getClass().getName();
		String x = "";
		if (substance.getClass().getName().equals("java.lang.String")
				&& text == null && ((String) substance).length() == 0) {
			x += "/string/-" + substance;
		}
		if (substance.getClass().getName().equals("java.util.Date")) {
			x += "/data/ " + HandleDate.acconciaData_F(
					(Date) substance);
		} else if (substance.getClass().getName().equals(
				"java.lang.Long")) {
			x += "/l/-" + substance;
		} else if (stub.equals("indexjsp")) {
			x += "/index/-navigation bar="
					+ ((JspKit) substance).navigationBar;
			/* it is not an error, it is the name of the class
			 * corresponding to long [] */
		} else if (substance.getClass().getName().equals(
				"it.ramecera.screwdriver.outfits.JspKit")) {
			x += "/jspKit/-" + ((JspKit) substance).toString();
			/* it is not an error, it is the name of the class
			 * corresponding to long [] */
		} else if (substance.getClass().getName().equals("[J")) {
			long[] d = (long[]) substance;
			for (long e : d) {
				x += "/tipo/-" + e;
			}
		}
		if (!x.isEmpty()) {
			s += "; substance = " + x;
		}
		s += Candies.LF;
		return s;
	}

}
