package OOXMLconvert;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;

/**
 *
 * @author ferou
 */
public class OOXMLnumberings {
	
	/** zoznam numberings id -> level -> bullet/number */
	private LinkedHashMap<String,ArrayList<LinkedHashMap<String,String>>> numberings = null; 
	/** zoznam obrazkov v tvare numPicBulletId -> relatívna cesta k obrázku */
	private LinkedHashMap<String,String> bulletPics = null;

	public OOXMLnumberings() {
		// noop
	}
	
	/**
	 * Funkcia uloží obrázok do potrebného formátu.
	 * @param image obrázok, ktorý chceme uložiť
	 * @param absPath absolútna cesta k výslednému súboru
	 * @param imgType typ obrázka
	 * @return <i>true</i> ak je element vizuálne prázdny <i>false</i> inak
	 */
	private boolean saveImage(BufferedImage image, String absPath, String imgType) {
		try {
			// vytvor súbor, kam sa obrázok uloží a zároveň vytvor všetky potrebné adresáre
			File outputFile = new File(absPath);
			outputFile.getParentFile().mkdirs();
			// na zaklade typu povodneho obrazka, uloz do zodpovedajuceho formatu
			if (imgType.trim().equals("jpg") || imgType.trim().equals("jpeg")) {
				// uloz obrazok ako jpg
				Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
				ImageWriter writer = (ImageWriter)iter.next();
				ImageWriteParam iwp = writer.getDefaultWriteParam();
				iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
				// nastav kvalitu
				float jpegQuality = Global.ooxmlParams.jpegQuality;
				iwp.setCompressionQuality(jpegQuality);
				// zapis do vystupneho suboru
				FileImageOutputStream output = new FileImageOutputStream(outputFile);
				writer.setOutput(output);
				IIOImage iioimage = new IIOImage(image, null, null);
				writer.write(null, iioimage, iwp);
				writer.dispose();
			}
			else if (imgType.trim().equals("png")) {
				// uloz obrazok ako png
				ImageIO.write(image, "png", outputFile);
			}
			else if (imgType.trim().equals("gif")) {
				// uloz obrazok ako gif
				ImageIO.write(image, "gif", outputFile);
			}
			return true;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (saveImage): " + e.toString());
			return false;
		}
	}
	
	/**
	 * Funkcia pripraví obrázok.
	 * @param imgFile súbor s obrázkom
	 * @param size dĺžka väčšej strany výsledného obrázka
	 * @return výsledná relatívna cesta k obrázku
	 */
	private String prepareImage(File imgFile, int size) {
		try {
			// priprav meno obrazka
			String relPath = Global.ooxmlParams.imgDir + imgFile.getName();
			String absPath = Functions.addEndSlash(Global.ooxmlParams.xhtmlDest.getParent()) + relPath;
			// zmen vsetky lomitka v ceste aby sa to na webe zobrazovalo spravne
			relPath = Functions.setWebSlashes(relPath);
			// povodny obrazok
			BufferedImage img = ImageIO.read(imgFile);
			int w = img.getWidth();
			int h = img.getHeight();
			double r = 1.0 * w / h;
			int width = 0;
			int height = 0;
			if (w >= h) {
				// klasicky obrazok
				width = size;
				height = ((Long)Math.round(size / r)).intValue();
			}
			else {
				// obrazok po vyske
				height = size;
				width = ((Long)Math.round(size / r)).intValue();
			}
			
			String imgType = Functions.getExtension(imgFile);
			
			// ak je to gif a je splnena velkostna tolerancia tak len skopiruj
			if ((imgType.equals("gif")) && Functions.almostEquals(w, width, 5) && Functions.almostEquals(h, height, 5)) {
				File out = new File(absPath);
				Functions.copyFile(imgFile, out);
				if (out.exists()) {
					return relPath;
				}
				else {
					return "";
				}
			}
			else {
				// novy vysledny obrazok
				BufferedImage scaledImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
				// zmensi obrazok na nastavenu velkost
				Graphics2D graphics = scaledImage.createGraphics();
				graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
				graphics.drawImage(img, 0, 0, width, height, null);
				if (saveImage(scaledImage, absPath, imgType)) {
					return relPath;
				}
				else {
					return "";
				}
			}
			
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (prepareImage): " + e.toString());
			return "";
		}
	}
	
	/**
	 * Funkcia prečíta obrázky v číslovaní a priradí k nim relatívne cesty k výsledným obrázkom
	 * @param root element obsahujúci číslovania
	 */
	private void readBulletPics(Element root) {
		try {
			bulletPics = new LinkedHashMap<String, String>();
			List children = root.getChildren("numPicBullet", Global.wns);
			Iterator iterator = children.iterator();
			while (iterator.hasNext()) {
				Element numPicBulletEl = (Element) iterator.next();
				String numPicBulletId = numPicBulletEl.getAttributeValue("numPicBulletId", Global.wns);
				Element pictEl = numPicBulletEl.getChild("pict", Global.wns);
				Element shapeEl = pictEl.getChild("shape", Global.vns);
				Element imagedataEl = shapeEl.getChild("imagedata", Global.vns);
				String imgId = imagedataEl.getAttributeValue("id", Global.rns);
				File imgFile = Global.ooxmlRels.numberingsImgFiles.get(imgId);
				// priprav obrazok
				String relPath = "";
				if (imgFile.exists()) {
					relPath = prepareImage(imgFile, 12);
				}
				if (!relPath.trim().isEmpty()) {
					bulletPics.put(numPicBulletId, relPath);
				}
			}
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (readBulletPics): " + e.toString());
		}
	}
	
	/**
	 * Funkcia prečíta všetky levely v danom numberings.
	 * @param elLevels element, ktorý je parentom levelov
	 */
	private ArrayList<LinkedHashMap<String,String>> readLevelStyles(Element elLevels) {
		try {
			ArrayList<LinkedHashMap<String,String>> levelStyles = new ArrayList<LinkedHashMap<String,String>>();
			// prejdi vsetky lvl elementy
			List children = elLevels.getChildren("lvl", Global.wns);
			Iterator iterator = children.iterator();
			while (iterator.hasNext()) {
				Element lvl = (Element) iterator.next();
				int ilvl = Functions.intValDef(lvl.getAttributeValue("ilvl",Global.wns), 0);
				// zober vsetkych potomkov tohto levelu a precitaj vlastnosti
				LinkedHashMap<String,String> style = new LinkedHashMap<String,String>();
				List lvlChildren = lvl.getChildren();
				Iterator iterator2 = lvlChildren.iterator();
				while (iterator2.hasNext()) {
					Element lvlChild = (Element) iterator2.next();
					if (lvlChild.getName().trim().equals("start")) {
						style.put("start", lvlChild.getAttributeValue("val", Global.wns));
					}
					else if (lvlChild.getName().trim().equals("numFmt")) {
						style.put("numFmt", lvlChild.getAttributeValue("val", Global.wns));
					}
					else if (lvlChild.getName().trim().equals("lvlText")) {
						style.put("lvlText", lvlChild.getAttributeValue("val", Global.wns));
					}
					else if (lvlChild.getName().trim().equals("lvlPicBulletId")) {
						style.put("lvlPicBulletId", lvlChild.getAttributeValue("val", Global.wns));
					}
				}
				levelStyles.add(ilvl, style);
			}
			return levelStyles;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (readLevelStyles): " + e.toString());
			return null;
		}
	}
	
	/**
	 * Funkcia vráti k danému numberingu a levelu jeho vlastnosti.
	 * @param numId numbering id
	 * @param lvl level
	 * @return linked hash map (zoznam jednotlivých vlastností)
	 */
	public LinkedHashMap<String,String> getNumbering(String numId, int lvl) {
		try {
			ArrayList<LinkedHashMap<String,String>> pom = numberings.get(numId);
			if (pom != null) {
				LinkedHashMap<String,String> retVal = pom.get(lvl);
				return retVal;
			}
			else {
				return null;
			}
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (getNumbering: "+numId+"): " + e.toString());
			return null;
		}
	}
	
	/**
	 * Funkcia prečíta abstraktné číslovania
	 * @param root element obsahujúci číslovania
	 * @return LinkedHashMap zoznam abstraktných číslovaní
	 */
	private LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>> readAbstNumberings(Element root) {
		try {
			LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>> abstNumbering = new LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>>();
			List childrenAbstrNum = root.getChildren("abstractNum", Global.wns);
			Iterator iteratorAbstrNum = childrenAbstrNum.iterator();
			while (iteratorAbstrNum.hasNext()) {
				Element child = (Element) iteratorAbstrNum.next();
				ArrayList<LinkedHashMap<String, String>> abstNum = new ArrayList<LinkedHashMap<String, String>>();
				String abstractNumId = "";
				abstractNumId = child.getAttributeValue("abstractNumId", Global.wns);
				abstNum = readLevelStyles(child);
				abstNumbering.put(abstractNumId, abstNum);
			}
			return abstNumbering;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (readAbstNumberings): " + e.toString());
			return null;
		}
	}
	
	/**
	 * Funkcia, ktorá prečíta overrides levelov
	 * @param numEl num element
	 */
	private void levelOverride(Element numEl) {
		try {
			String numId = numEl.getAttributeValue("numId", Global.wns);
			List children = numEl.getChildren("lvlOverride", Global.wns);
			Iterator iterator = children.iterator();
			while (iterator.hasNext()) {
				LinkedHashMap<String, String> lvlOverride = new LinkedHashMap<String, String>();
				Element lvlOverrideEl = (Element) iterator.next();
				int lvl = Functions.intValDef(lvlOverrideEl.getAttributeValue("ilvl", Global.wns), -1);
				ArrayList<LinkedHashMap<String,String>> ls = readLevelStyles(lvlOverrideEl);
				if ((ls != null) && (ls.size() > lvl)) {
					lvlOverride = ls.get(lvl);
				}
				if (lvlOverride != null) {
					this.numberings.get(numId).add(lvl, lvlOverride);
				}
			}
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (levelOverride): " + e.toString());
		}
	}
	
	/**
	 * Funkcia prečíta číslovania
	 * @param root element obsahujúci číslovania
	 * @param abstNumbering zoznam abstraktných číslovaní
	 */
	private void readNumberings(Element root, LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>> abstNumbering) {
		try {
			List children = root.getChildren("num", Global.wns);
			Iterator iterator = children.iterator();
			while (iterator.hasNext()) {
				Element numEl = (Element) iterator.next();
				String numId = numEl.getAttributeValue("numId", Global.wns);
				// spoj so zodpovedajucim abstract numbering
				// zisti abstractNumId
				Element abstractNumIdEl = numEl.getChild("abstractNumId", Global.wns);
				String abstractNumId = abstractNumIdEl.getAttributeValue("val",Global.wns);
				this.numberings.put(numId, abstNumbering.get(abstractNumId));
				// override levelov
				levelOverride(numEl);
			}
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (readNumberings): " + e.toString());
		}
	}
	
	/**
	 * Funkcia prečíta štýly číslovania a zapíše ich do numberings.
	 * @return <i>true</i> ak nenastala chyba, <i>false</i> inak
	 */
	public boolean readAllNumberings() {
		try {
			File numberingsFile = Global.ooxmlRels.numberingFile;
			if ((numberingsFile != null) && (numberingsFile.exists())) {
				LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>> abstNumbering = new LinkedHashMap<String, ArrayList<LinkedHashMap<String, String>>>();
				numberings = new LinkedHashMap<String,ArrayList<LinkedHashMap<String,String>>>();
				SAXBuilder builder = new SAXBuilder();
				Document doc = builder.build(numberingsFile);
				Element root = doc.getRootElement();
				// nacitaj bullet obrazky
				if (Global.ooxmlPreserve.bulletStyle) {
					readBulletPics(root);
				}
				// nacitaj abstraktne numberings
				abstNumbering = readAbstNumberings(root);
				// nacitaj numbering
				readNumberings(root, abstNumbering);
			}
			return true;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (readAllNumberings): " + e.toString());
			return false;
		}
	}
	
	/**
	 * Funkcia vráti na základe kódu znaku v odrážke CSS obdobu
	 * @param code kód znaku v odrážke
	 * @return String v ktorom je CSS štýl
	 */
	public String getBulletStyle(int code) {
		try {
			String retStr = "";
			String bulletStyle = "disc";
			switch (code) {
				case 61623:
					// plna gulicka
					bulletStyle = "disc";
					break;
				case 111:
					// prazdna gulicka
					bulletStyle = "circle";
					break;
				case 61607:
					// stvorcek
					bulletStyle = "square";
					break;
				case 61558:
					// stvorlistok
					bulletStyle = "disc";
					break;
				case 61656:
					// sipka
					bulletStyle = "disc";
					break;
				case 61692:
					// fajocka
					bulletStyle = "disc";
					break;
				default:
					bulletStyle = "disc";
					break;
			}
			retStr = Global.cssOutput.addClassN(".bullet", "list-style-type: " + bulletStyle + ";");
			return retStr;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (getNumStyle): " + e.toString());
			return "";
		}
	}
	
	/**
	 * Funkcia vráti na základe štýlu číslovania CSS obdobu
	 * @param ooxmlStyle štýl číslovania v OOXML
	 * @return String v ktorom je CSS štýl
	 */
	public String getNumberingStyle(String ooxmlStyle) {
		try {
			String retStr = "";
			String numStyle = "decimal";
			ooxmlStyle = ooxmlStyle.trim();
			if (ooxmlStyle.equals("decimal")) {
				numStyle = "decimal";
			}
			else if (ooxmlStyle.equals("lowerLetter")) {
				numStyle = "lower-latin";
			}
			else if (ooxmlStyle.equals("lowerRoman")) {
				numStyle = "lower-roman";
			}
			else if (ooxmlStyle.equals("upperRoman")) {
				numStyle = "upper-roman";
			}
			else if (ooxmlStyle.equals("upperLetter")) {
				numStyle = "upper-latin";
			}
			else {
				numStyle = "decimal";
			}
			retStr = Global.cssOutput.addClassN(".bullet", "list-style-type: " + numStyle + ";list-style-image: none;");
			return retStr;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (getNumberingStyle): " + e.toString());
			return "";
		}
	}
	
	/**
	 * Funkcia vráti na základe id obrázka CSS štýl pre danú odrážku
	 * @param imgId id obrázka, pre ktorý chceme získať štýl
	 * @return String v ktorom je CSS štýl
	 */
	public String getImgNumberingStyle(String imgId) {
		try {
			String retStr = "";
			String imgPath = bulletPics.get(imgId);
			if ((imgPath != null) && (!imgPath.trim().isEmpty())) {
				retStr = Global.cssOutput.addClassN(".bullet", "list-style-type: disc;list-style-image: url('" + Functions.getRelativePathToRoot(Global.ooxmlParams.cssDir) + imgPath + "');");
			}
			return retStr;
		}
		catch (Exception e) {
			System.err.println(this.getClass().getName() + " (getImgNumberingStyle): " + e.toString());
			return "";
		}
	}
	
}