//
// 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.salad.analysis;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import it.ramecera.salad.everyday.Candies;
import it.ramecera.salad.everyday.ExactException;
import it.ramecera.salad.everyday.Mix;
import it.ramecera.salad.everyday.Strings;
import it.ramecera.salad.file.Equipment;

/**
 * <p>
 * Elementary structure of a Java class; interfaces and inner classes
 * are excluded.
 * </p>
 * <p>
 * Creation date: 2020-04-09<br>
 * modification date: 2020-04-09<br>
 * Company: Ramecera™
 * </p>
 * <p>
 * Modifications:<br>
 * </p>
 * 
 * @author M. Maglioni */
public class Flute {

	// PUBLIC PROPERTIES

	/** the file to be analyzed */
	public File file;
	/** the package */
	public String fpackage;
	/** if it is abstract */
	public boolean fabstract;
	/** the list of imports, without the word import and without a final
	 * semicolon */
	public List<String> fimportE = new ArrayList<String>();
	/** class name */
	public String fclass;
	/** the extends */
	public String fextends;
	/** the implementations */
	public String fimplements;
	/** visibility: public = 3 default = 1 */
	public int fvisibilita;
	/** if it is final */
	public boolean ffinal;
	/** if it is static */
	public boolean fstatic;
	/** class annotations */
	public List<JavaAnnotation> fannotationE;
	/** list of class properties */
	public List<JavaProperty> fpropertyE = new ArrayList<JavaProperty>();
	/** list of class methods */
	public List<JavaMethod> fmethodE = new ArrayList<JavaMethod>();

	/** scope of the properties of a Java class */
	public static final String[] VISIBILITA = { "default", "private",
			"protected", "public" };

	// PRIVATE PROPERTY

	private String t;
	private int penna;

	/** conventional sign */
	final static private char BELL = 7;
	final static private String BELLS = String.valueOf(BELL);
	/** conventional sign */
	final static private String ALFA = "alfa" + BELLS;
	/** conventional sign */
	final static private String BETA0 = "beta0" + BELLS;
	/** conventional sign */
	final static private String BETA1 = "beta1" + BELLS;
	/** conventional sign */
	final static private String GAMMA0 = "gamma0" + BELLS;
	/** conventional sign */
	final static private String GAMMA1 = "gamma1" + BELLS;
	/** quotes */
	final static private char VIRGOLETTE = 34;
	/** open parenthesis */
	final static private char PARENTESI_APERTA = 40;
	/** open parenthesis */
	final static private char PARENTESI_CHIUSA = 41;
	/** conventional sign */
	final static private String CAVA0 = "(flax28x7b";
	/** conventional sign */
	final static private String CAVA1 = "flax7dx29)";
	/** conventional sign */
	final static private String ALT_CHIO = "flax40";

	/** @param file file to be analyzed */
	public Flute(File file) {
		this.file = file;
	}

	/** @param g file to be analyzed */
	public Flute(final String g) {
		this.file = new File(g);
	}

	/**
	 * <p>
	 * Analysis of a .java file to extract its salient elements.
	 * </p>
	 * <p>
	 * Creation date: 2020-04-09<br>
	 * modification date: 2020-04-09<br>
	 * Company: Ramecera™
	 * </p>
	 * <p>
	 * Modifications:<br>
	 * </p>
	 * 
	 * @author M. Maglioni
	 * @throws IOException standard exception
	 * @throws FileNotFoundException exceptionals standard
	 * @throws ExactException standard exception */
	final public void analyze()

			throws FileNotFoundException, IOException, ExactException {

		t = Equipment.oneString(file);
		/* I remove all comments, including those of javadoc; I foresee
		 * both the case and the end-line is CR + LF, and the case of
		 * only LF */
		t = t.replaceAll("\\x2f\\x2f.+\\x0d", "");
		t = t.replaceAll("\\x2f\\x2f.+\\x0a", "");
		t = Strings.compact_A(t);
		/* with the combination. +? I intend to capture all the
		 * characters until the next end-comment notification, ie x2ax2f
		 * (asterisk-slash); if instead I had written only. +, he would
		 * have gone to capture the last end-comment */
		t = t.replaceAll("\\x2f\\x2a.+?\\x2a\\x2f", "");
		t = t.replaceAll("\\x5c\\x22", ALFA); // backslash and quotes
		// equal and opening brace
		t = t.replaceAll("\\x3d\\s?\\x7b", "=" + GAMMA0);
		// closing brace and semicolon
		t = t.replaceAll("\\x7d\\s?\\x3b", GAMMA1 + ";");
		// closing brace and comma
		t = t.replaceAll("\\x7d\\s?\\x2c", GAMMA1 + ",");
		// closing brace and closed round bracket
		t = t.replaceAll("\\x7d\\s?\\x29", GAMMA1 + ")");
		int virgoletteQ = Mix.charQ(t, VIRGOLETTE);
		if (virgoletteQ % 2 != 0) {
			ExactException ce = new ExactException();
			ce.className = fclass
					+ " (analized by it.ramecera.salad.analysis.Flute)";
			ce.code = 20060001;
			throw ce;
		}
		/* there must be at least one opening brace */
		int ga = t.indexOf(123);
		if (ga == -1) {
			ExactException ce = new ExactException();
			ce.className = fclass
					+ " (analized by it.ramecera.salad.analysis.Flute)";
			ce.code = 20060002;
			throw ce;
		}
		char punto_e_virgola = 59;
		interni(BETA0, punto_e_virgola);
		char chiusa_parentesi = 41;
		interni(BETA1, chiusa_parentesi);
		/* the prologue goes from the beginning of the file to the
		 * declaration of the name of the class included */
		String prologo = t.substring(0, ga);
		String[] q = prologo.split("\\x3b"); // 3b is semicolon
		if (!q[0].trim().startsWith("package")) {
			ExactException ce = new ExactException();
			ce.className = fclass
					+ " (analized by it.ramecera.salad.analysis.Flute)";
			ce.code = 20060004;
			throw ce;
		} else {
			fpackage = q[0].replaceFirst("\\s*package\\s", "").trim();
		}
		for (int m = 1; m < q.length - 1; m++) {
			String x = q[m].replaceFirst("\\s*import\\s", "");
			fimportE.add(x.trim());
		}
		String centro0 = q[q.length - 1].trim();
		fannotationE = annota(centro0);
		String centro1 = centro0.substring(penna);
		int impleJ = centro1.indexOf(" implements ");
		int extJ = centro1.indexOf(" extends ");
		String centro2 = null;
		if (impleJ > -1 & extJ == -1) {
			centro2 = centro1.substring(0, impleJ);
			/* 12 is the length of the string "implements" */
			fimplements = centro1.substring(impleJ + 12);
		} else if (impleJ == -1 & extJ > -1) {
			centro2 = centro1.substring(0, extJ);
			/* 9 is the length of the string "extends" */
			fextends = centro1.substring(extJ + 9);
		} else if (impleJ > -1 & extJ > -1) {
			if (impleJ < extJ) {
				ExactException ce = new ExactException();
				ce.className = fclass
						+ " (analized by it.ramecera.salad.analysis.Flute)";
				ce.code = 20060006;
				throw ce;
			}
			centro2 = centro1.substring(0, extJ);
			/* 9 is the length of the string "extends" */
			fextends = centro1.substring(extJ + 9, impleJ);
			/* 12 is the length of the string "implements" */
			fimplements = centro1.substring(impleJ + 12);
		}
		String[] b1 = Strings.parole_A(centro2);
		for (int k = 0; k < b1.length; k++) {
			if (b1[k].equals("static")) {
				fstatic = true;
			} else if (b1[k].equals("final")) {
				ffinal = true;
			} else if (b1[k].equals("abstract")) {
				fabstract = true;
			} else if (b1[k].equals("public")) {
				fvisibilita = 3;
			} else if (b1[k].substring(0, 1).matches("\\p{Upper}")) {
				fclass = b1[k];
			}
		}
		/* the body is what follows the prologue (see above), then from
		 * the first clip, which opens the course of the class,
		 * onwards */
		String corpo = t.substring(ga + 1);
		/* in the body I have to correct the existence of complex
		 * annotations that contain round braces that braces, open and
		 * closed */
		corpo = corpo.replaceAll("\\x28\\x7b", CAVA0);
		corpo = corpo.replaceAll("\\x7d\\x29", CAVA1);
		String[] r = corpo.split("\\x7d"); // 7d is the closing brace
		for (int m = 0; m < r.length - 1; m++) {
			int i = r[m].indexOf(123); // 123 is the opening brace
			if (i == -1) {
				ExactException ce = new ExactException();
				ce.className = fclass
						+ " (analized by it.ramecera.salad.analysis.Flute)";
				ce.code = 20060007;
				throw ce;
			} else {
				JavaMethod method = new JavaMethod();
				method.grezzo0 = r[m].substring(0, i).trim();
				String[] p0 = method.grezzo0.split("\\x3b"); // x3b =;
				for (int j = 0; j < p0.length - 1; j++) {
					JavaProperty property = new JavaProperty();
					String grezzo5 = p0[j];
					int pareAperteQ = Mix.charQ(grezzo5,
							PARENTESI_APERTA);
					int pareChiuseQ = Mix.charQ(grezzo5,
							PARENTESI_CHIUSA);
					if (pareAperteQ != pareChiuseQ) {
						ExactException ce = new ExactException();
						ce.className = fclass
								+ " (analized by it.ramecera.salad.analysis.Flute)";
						ce.code = 20060003;
						throw ce;
					}
					int ugualeJ = -1;
					do {
						/* 61 is the same */
						ugualeJ = grezzo5.indexOf(61, ugualeJ + 1);
						if (ugualeJ == -1) {
							break;
						}
						String prima = grezzo5.substring(0, ugualeJ);
						pareAperteQ = Mix.charQ(prima,
								PARENTESI_APERTA);
						pareChiuseQ = Mix.charQ(prima,
								PARENTESI_CHIUSA);
					} while (pareAperteQ > pareChiuseQ);
					if (ugualeJ > -1) {
						property.fvalore = ripristina(grezzo5.substring(
								ugualeJ + 1));
						if (property.fvalore.startsWith("new ")) {
							property.fnew = property.fvalore.substring(
									4);
							property.fvalore = null;
						}
						grezzo5 = grezzo5.substring(0, ugualeJ);
					}
					property.fannotationE = annota(grezzo5);
					if (property.fannotationE.size() > 0) {
						grezzo5 = grezzo5.substring(penna - 1);
					}
					String[] b2 = Strings.parole_A(grezzo5);
					/* the propery name is the last word */
					property.fnome = b2[b2.length - 1];
					for (int k = 0; k < b2.length - 1; k++) {
						if (b2[k].endsWith("[]")) {
							b2[k] = b2[k].substring(0, b2[k].length()
									- 2);
							property.quadre = true;
						}
						if (b2[k].equals("static")) {
							property.fstatic = true;
						} else if (b2[k].equals("final")) {
							property.ffinal = true;
						} else if (b2[k].equals("abstract")) {
							property.fabstract = true;
						} else if (b2[k].equals("public")) {
							property.fvisibilita = 3;
						} else if (b2[k].equals("protected")) {
							property.fvisibilita = 2;
						} else if (b2[k].equals("private")) {
							property.fvisibilita = 1;
						} else if (b2[k].equals("int") | b2[k].equals(
								"double") | b2[k].equals("byte") | b2[k]
										.equals("short") | b2[k].equals(
												"float") | b2[k].equals(
														"boolean")
								| b2[k].equals("char") | b2[k].equals(
										"long")) {
							property.fprimitivo = b2[k];
						} else if (b2[k].substring(0, 1).matches(
								"\\p{Upper}")) {
							property.fclasse = b2[k];
						}
					}
					fpropertyE.add(property);
				}
				method.testata = p0[p0.length - 1];
				method.row1 = r[m].substring(i + 1).trim();
				if (!method.row1.equals("")) {
					// 3b is the semicolon
					String[] p1 = method.row1.split("\\x3b");
					for (int j = 0; j < p1.length; j++) {
						String ist = ripristina(p1[j]);
						method.istruzioneE.add(ist);
					}
				}
				fmethodE.add(method);
			}
		}
		for (int j = 0; j < fannotationE.size(); j++) {
			JavaAnnotation a = fannotationE.get(j);
			if (a.fargument != null) {
				a.tokenE = a.fargument.split("\\x2c"); // x2c is comma
				for (int k = 0; k < a.tokenE.length; k++) {
					String x = a.tokenE[k].trim();
					a.tokenE[k] = x.trim();
					String[] d = new String[2];
					int m = x.indexOf(Candies.EQUALITY_SIGN);
					if (m > -1) {
						d[0] = x.substring(0, m).trim();
						d[1] = x.substring(m + 1).trim();
						if (d[1].startsWith("\"") & d[1].endsWith("\"")
								& d[1].length() > 1) {
							d[1] = d[1].substring(1, d[1].length() - 1);
						}
						if (a.paramE.containsKey(d[0])) {
							ExactException ce = new ExactException();
							ce.code = 20060008;
							ce.className = fclass
									+ " (analized by it.ramecera.salad.analysis.Flute)";
							ce.datum = "The parameters of annotation @"
									+ a.fnome + " aren't unique: " + x
									+ ".";
							throw ce;
						} else {
							a.paramE.put(d[0], d[1]);
						}
					}
				}
			}
		}
	}

	final private void interni(final String tappo, final char c) {
		int j0 = 0, j1 = 0;
		// int k = 0;
		j0 = t.indexOf(34, 0); // 34 are the quotes "
		j1 = t.indexOf(34, j0 + 1);
		while (j0 > -1) {
			// k ++;
			int n = t.indexOf(c, j0);
			/* if there is and is less than the next ... */
			if (n > -1 & n < j1) {
				/* ... then I found an internal character */
				t = t.substring(0, n) + tappo + t.substring(n + 1);
				j1 += tappo.length() - 1;
			}
			j0 = t.indexOf(34, j1 + 1);
			j1 = t.indexOf(34, j0 + 1);
		}
	}

	final private List<JavaAnnotation> annota(final String a0)

			throws ExactException {

		String a = a0;
		List<JavaAnnotation> aE = new ArrayList<JavaAnnotation>();
		/* remediation of all annotations nested in correspondence of
		 * the pair CAVA0 and CAVA1 " */
		String pezzo0 = null;
		String pezzo1 = null;
		String pezzo2 = null;
		int nc0 = a.indexOf(CAVA0);
		int nc1 = a.indexOf(CAVA1);
		if (nc0 > 1 & nc1 > 1) {
			pezzo0 = a.substring(0, nc0);
			pezzo1 = a.substring(nc0, nc1);
			pezzo2 = a.substring(nc1);
			pezzo1 = pezzo1.replaceAll("\\x40", ALT_CHIO);
			a = pezzo0 + pezzo1 + pezzo2;
		}
		int j0 = 0, j1 = 0;
		// int k = 0;
		j0 = a.indexOf(64, 0); // 64 is the snail @
		j1 = a.indexOf(64, j0 + 1);
		/* now I have to correct the value of j1, because there might be
		 * nested annotations; I don't have to stop on snails that are
		 * inside the pair of braces */
		/* GAMMA1 is the closing brace */
		int g0 = a.indexOf(GAMMA1 + ")", j0 + 1);
		/* if j1 <g0, the nut found is not good, you have to get to the
		 * next one */
		while (j1 < g0 & j1 != -1) {
			j1 = a.indexOf(64, j1 + 1);
		}
		/* loop on the snails, it is used to examine the various
		 * annotations */
		while (j0 > -1) {
			/* for each annotation I have the index j0 which points to
			 * the scroll, but also j1 which points to the next */
			// k ++;
			int pta0 = a.indexOf(40, j0); // 40 is the open parenthesis
			JavaAnnotation z = null;
			/* if the open round bracket is and is less than the next
			 * ... */
			if (pta0 > -1 & (pta0 < j1 | j1 == -1)) {
				/* ... then I found a topic, I look for the closing
				 * parenthesis */
				/* 41 is the closing parenthesis */
				int ptc0 = a.indexOf(41, pta0);
				/* but now I have to correct the value of ptc0 because I
				 * need not the next closed parenthesis, but the one
				 * that really corresponds to a closing; in fact it is
				 * necessary to take into account the nested
				 * annotations */
				int ptc1 = a.indexOf(GAMMA1 + ")", pta0);
				/* if I found GAMMA1), that's the good value */
				if (ptc1 > -1) {
					ptc0 = ptc1;
				}
				if (ptc0 == -1 | (ptc0 > j1 & j1 > -1)) {
					ExactException ce = new ExactException();
					ce.code = 20060005;
					ce.className = fclass
							+ " (analized by it.ramecera.salad.analysis.Flute)";
					throw ce;
				}
				penna = ptc0;
				/* the instantiated annotation, in the case of nested
				 * annotations, is the most external; the internal ones
				 * are part of the internal material (topic properties)
				 * of the first */
				z = new JavaAnnotation(a.substring(j0 + 1, pta0), a
						.substring(pta0 + 1, ptc0));
			} else if (j1 == -1) {
				/* if it isn't there, and we are at the last
				 * annotation */
				penna = a.indexOf(32, j0);
				z = new JavaAnnotation(a.substring(j0 + 1, penna),
						null);
			} else {
				/* if it is not there, and we are not at the last
				 * annotation */
				penna = j1 - 1;
				z = new JavaAnnotation(a.substring(j0 + 1, penna),
						null);
			}
			aE.add(z);
			/* in the first search there is no +1 because, unlike the
			 * quotes, I don't have to look for separate couples, but a
			 * couple that starts with the second element of the
			 * previous couple */
			if (j1 == -1) {
				j0 = -1;
			} else {
				j0 = a.indexOf(64, j1);
			}
			j1 = a.indexOf(64, j0 + 1);
		}
		penna += 2;
		return aE;
	}

	String ripristina(final String s) {
		String r = s;
		r = r.replaceAll(ALFA, "\"");
		r = r.replaceAll(BETA0, ";");
		r = r.replaceAll(BETA1, ")");
		r = r.replaceAll(GAMMA0, "{");
		r = r.replaceAll(GAMMA1, "}");
		return r.trim();
	}
}
