/*
 * $Id$
 *
 * JSDAI(TM), a way to implement STEP, ISO 10303
 * Copyright (C) 1997-2008, LKSoftWare GmbH, Germany
 *
 * 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 (AGPL v3).
 *
 * 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/>.
 *
 * JSDAI is a registered trademark of LKSoftWare GmbH, Germany
 * This software is also available under commercial licenses.
 * See also http://www.jsdai.net/
 */

package jsdai.tools;

import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.lksoft.util.ArgumentsParser;

import jsdai.SExtended_dictionary_schema.AEntity_declaration;
import jsdai.SExtended_dictionary_schema.EEntity_declaration;
import jsdai.SExtended_dictionary_schema.EEntity_definition;
import jsdai.SExtended_dictionary_schema.EReferenced_declaration;
import jsdai.SExtended_dictionary_schema.EUsed_declaration;
import jsdai.lang.*;

public final class ConformanceOptionsGenerator {
/* Remove this comment if you want to use this class
	private static final String REPOSITORY_NAME = "ExpressCompilerRepo3";
	private static final String ARM_REPOSITORY_LOCATION = "D:\\workspace\\stepmod_LKSOFT\\modules\\stepmod\\build_arm2\\repositories\\ExpressCompilerRepo2.sdai";
	private static final String MIM_REPOSITORY_LOCATION = "D:\\workspace\\stepmod_LKSOFT\\modules\\stepmod\\build_aim2\\repositories\\ExpressCompilerRepo3.sdai";
	static SdaiRepository reposARM = null;
	static SdaiRepository reposMIM = null;
	private static Map<String, List> armEntityNameToCCO;
	private static Map<String, List> mimEntityNameToCCO;
	private static Map<String, ConformanceOption> coIdToCO;
	
	private ConformanceOptionsGenerator() { }

	private static File[] parseArgs(String[] args)throws IOException {
		ArgumentsParser.Value ccsArg = new ArgumentsParser.Value("-ccs", true);
		ccsArg.setValueName("ccs");
		ccsArg.setDescription("ccs.xml file containing input information for conformance classes. Example: c:/workspace/stepmod/data/application_protocols/config_control_design_ed2/ccs.xml");

		ArgumentsParser.Value coMimArg = new ArgumentsParser.Value("-comim", true);
		coMimArg.setValueName("comim");
		coMimArg.setDescription("comim.xml file containing mim_in_cc/co stuff. Example: c:/workspace/stepmod/data/application_protocols/config_control_design_ed2/comim.xml");

		ArgumentsParser.Value coArmArg = new ArgumentsParser.Value("-coarm", true);
		coMimArg.setValueName("coarm");
		coMimArg.setDescription("coarm.xml containing arm_in_cc/co stuff. Example: c:/workspace/stepmod/data/application_protocols/config_control_design_ed2/coarm.xml");
		
		List argList = Arrays.asList(new Object[] {ccsArg, coArmArg, coMimArg});
		if (!ArgumentsParser.parse(args, argList)) {
			System.out.println(ArgumentsParser.getUsage(ConformanceOptionsGenerator.class.getName(), argList));
			return null;
		}


		File ccsFile = new File(ccsArg.getValue());
		if (!ccsFile.exists()) {
			System.out.println("Specified ccs.xml file does not exists." + ccsFile+" EXIT the program ");
			return null;
		}

		File coArmFile = new File(coArmArg.getValue());
		
		File coMimFile = new File(coMimArg.getValue());
		File[] files = {ccsFile, coArmFile, coMimFile};
		return files;
	}

	public static void main(String[] args)
		throws SdaiException {
		boolean success = false;		
		try {
			File[] parsedArgs = parseArgs(args);
			if (parsedArgs == null) {
				return;
			}
			armEntityNameToCCO = new HashMap<String, List>();
			mimEntityNameToCCO = new HashMap<String, List>();
			coIdToCO = new HashMap<String, ConformanceOption>();
			File ccsFile = parsedArgs[0];
			File coARMFile = parsedArgs[1];
			File coMimFile = parsedArgs[2];
			
//			System.out.println("Opening repo: " + REPOSITORY_NAME);
//			SdaiRepository repo = Utils.getRepo(REPOSITORY_NAME);
//			if (repo != null) {
				System.out.println("Parsing ccs.xml ...");
				success = parseXMLandGenerateCOs(ccsFile, coARMFile.getAbsolutePath(), coMimFile.getAbsolutePath());
//			}
		} catch (IOException e) {
			e.printStackTrace(); 
		} finally {
			SdaiSession session = SdaiSession.getSession();
			if (session != null) {
				session.closeSession();
			}
		}

		if (success) {
			System.out.println("Success.");
		} else {
			System.out.println("Failed.");
		}
	}

	private static boolean parseXMLandGenerateCOs(File ccsFile, String coARMFileName, String coMIMFileName) {
		// Need 2 documents as we want to smoothly include them into master ccs.xml
		Document coDocARM = null;
		Document coDocMIM = null;
		Document ccsDoc = null;
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			docFactory.setValidating(false);
			docFactory.setCoalescing(true);
			// CCS file
			DocumentBuilder builder = docFactory.newDocumentBuilder();
			ccsDoc = builder.parse(ccsFile);
			// CO file
			coDocARM = builder.newDocument();
			coDocMIM = builder.newDocument();
			Element root = ccsDoc.getDocumentElement();
			// String topModuleName = root.getAttribute("module");
			//Element coRoot = coDoc.createElement("conformance");
			//coRoot.setAttribute("module", topModuleName);
			Element arms_in_ccs = coDocARM.createElement("arms_in_ccs");
			coDocARM.appendChild(arms_in_ccs);
			// coDoc.appendChild(coRoot);
			Element mims_in_ccs = coDocMIM.createElement("mims_in_ccs");
			coDocMIM.appendChild(mims_in_ccs);
			//coDoc.appendChild(mims_in_ccs);

			startSession();			
			
			NodeList nodes = root.getChildNodes();
			// System.err.println(" Root "+topModuleName);
			for(int rIndex=0; rIndex < nodes.getLength(); rIndex++){
				Node node = nodes.item(rIndex);
				if(node.getNodeName().equals("cc")){
					NodeList ccNodes = node.getChildNodes();
					Element coElement = (Element)node;
					String ccId = coElement.getAttribute("id");
					ConformanceClass cc =  new ConformanceClass(ccId);
					for(int ccIndex=0; ccIndex < ccNodes.getLength(); ccIndex++){
						Node ccNode = ccNodes.item(ccIndex);
						String nodeName = ccNode.getNodeName();
						if(nodeName.equals("module")){
							Element moduleNode = (Element)ccNode;
							String moduleName = moduleNode.getAttribute("name");
							processCCOModule(cc, moduleNode, true);
							processCCOModule(cc, moduleNode, false);
						}
					}
				}else if(node.getNodeName().equals("co")){
					NodeList coNodes = node.getChildNodes();
					Element coElement = (Element)node;
					String coName = coElement.getAttribute("name");
					ConformanceOption co =  new ConformanceOption(coName);
					coIdToCO.put(coName, co);
					for(int coIndex=0; coIndex < coNodes.getLength(); coIndex++){
						Node coNode = coNodes.item(coIndex);
						String nodeName = coNode.getNodeName();
						if(nodeName.equals("module")){
							Element moduleNode = (Element)coNode;
							processCCOModule(co, moduleNode, true);
							processCCOModule(co, moduleNode, false);
							// Process separate entities
														
							// System.err.println(co.getAttribute("id")+" = "+moduleNode.getAttribute("name"));	
						}else if(nodeName.equals("arm_entity")){
							String entityName = parseEntityName(coNode);
							putNameCoLink(co, entityName, armEntityNameToCCO);
							co.addArmEntity(entityName);
							// System.err.println(entityName+" -> "+co);
						}else if(nodeName.equals("mim_entity")){
							String entityName = parseEntityName(coNode);
							putNameCoLink(co, entityName, mimEntityNameToCCO);
							co.addMimEntity(entityName);
							// System.err.println(entityName+" -> "+co);
						// Due to recursive nature of this reference - it will be processed later.	
						}else if(nodeName.equals("co_ref")){
							Element coIdNode = (Element)coNode;
							co.addCo(coIdNode.getAttribute("name"));
						}else if(nodeName.equals("#text")){
							// DO nothing here
						}else{
							System.err.println("Unsupported type of element in CO - "+nodeName);
						}
					}
				}
			}
			// Process possible links COs to other COs
			processCOLinks();
			create_x_in_ccs(coDocARM, arms_in_ccs, true);
			create_x_in_ccs(coDocMIM, mims_in_ccs, false);
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SdaiException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			Utils.saveToFile(coARMFileName, coDocARM, null);
			Utils.saveToFile(coMIMFileName, coDocMIM, null);
			// Utils.saveToFile(ccsFile.getAbsolutePath(), ccsDoc, null);
			// System.err.println(coFileName+" 0 "+codtdFileName+" "+coDoc);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			System.out.println("PROBLEM: " + e.getMessage());
			e.printStackTrace();
		}

		return true;
	}

	private static void processCOLinks()throws SdaiException {
		Set keys = coIdToCO.keySet();
		Object[] cos = keys.toArray();
		// Collect all entities systematically
		for(int i=0; i<cos.length; i++){
			String coId = (String)cos[i];
			ConformanceOption co = coIdToCO.get(coId);
			// Search for leaves
			if(!co.containsCOs()){
				co.setFullyProcessed(true);
			// Now need to recursively process the references	
			}else{
				processCoRecursive(co);
			}
		}
		// Now we can create missing arm_in_ccs/mim_in_ccs
		for(int i=0; i<cos.length; i++){
			String coId = (String)cos[i];
			ConformanceOption co = coIdToCO.get(coId);
			// Only those having COs may have something missing
			if(co.containsCOs()){
				List<String> armEntities = co.getArmEntities();
				for(int j=0,entitiesCount=armEntities.size(); j<entitiesCount; j++){
					putNameCoLink(co, armEntities.get(j), armEntityNameToCCO);
				}
				List<String> mimEntities = co.getMimEntities();
				for(int j=0,entitiesCount=mimEntities.size(); j<entitiesCount; j++){
					putNameCoLink(co, mimEntities.get(j), mimEntityNameToCCO);
				}
			}
		}
	}

	private static void processCoRecursive(ConformanceOption co) {
		List<String> coIds = co.getCos();
		for(int j=0,cosCount=coIds.size(); j<cosCount; j++){
			String coId = coIds.get(j); 
			ConformanceOption coRef = coIdToCO.get(coId);
			if(coRef == null){
				System.err.println("No referenced module found - "+coId);
				continue;
			}
			// System.err.println(co+" "+j+" "+coId+" "+coRef);
			if((coRef.containsCOs())&&(!coRef.isFullyProcessed())){
				processCoRecursive(coRef);
			}
			co.processCoReference(coRef);
		}
		co.setFullyProcessed(true);
	}

	private static String parseEntityName(Node node) {
		NodeList nodes = node.getChildNodes();
		// Assumption - there must be eaxactly one node
		// System.err.println(" Node "+nodes.item(1).getNodeValue());
		Element expressRef = (Element)nodes.item(1);
		String name = expressRef.getAttribute("linkend");
		StringTokenizer st = new StringTokenizer(name, ":.");
		String result = null;
		while(st.hasMoreElements()){
			result = (String)st.nextElement();
		}
		return result;
	}

	private static void processCCOModule(Object key, Element moduleNode, boolean isARM)throws SdaiException {
		String moduleName = moduleNode.getAttribute("name");
		String schemaName;
		SdaiSession session = SdaiSession.getSession();
		SdaiRepository repos;
		Map<String, List> entityNameToCCO;
		if(isARM){
			schemaName = (moduleName+"_arm_dictionary_data").toUpperCase();
			if(reposARM == null){
				reposARM = repos = session.linkRepository(REPOSITORY_NAME, ARM_REPOSITORY_LOCATION);
				repos.openRepository();
			}else{
				repos = reposARM;
			}
			entityNameToCCO = armEntityNameToCCO;
		}else{
			schemaName = (moduleName+"_mim_dictionary_data").toUpperCase();
			if(reposMIM == null){
				reposMIM = repos = session.linkRepository(REPOSITORY_NAME, MIM_REPOSITORY_LOCATION);
				repos.openRepository();				
			}else{
				repos = reposMIM;
			}
			
			entityNameToCCO = mimEntityNameToCCO;
		}
			// Process Module
		SdaiModel topModel = repos.findSdaiModel(schemaName);
		if(topModel == null){
			System.err.println(" No schema found "+schemaName);
			return;
		}
		if(topModel.getMode() != SdaiModel.READ_ONLY){			
			topModel.startReadOnlyAccess();
		}
		AEntity_declaration declarations = (AEntity_declaration)topModel.getInstances(EEntity_declaration.class);
		String completness = moduleNode.getAttribute("completness");
		// Form a list of declarations to remove
		List<String> declarationsToSkip = new ArrayList<String>(); 
		if(completness.equals("substractive")){
			NodeList moduleNodes = moduleNode.getChildNodes();
			for(int moIndex=0; moIndex < moduleNodes.getLength(); moIndex++){
				Node moNode = moduleNodes.item(moIndex);
				String nodeName = moNode.getNodeName();
				if(nodeName.equals("arm_entity")){
					if(isARM){
						String entityName = parseEntityName(moNode);
						declarationsToSkip.add(entityName);
					}
				}else if(nodeName.equals("mim_entity")){
					if(!isARM){
						String entityName = parseEntityName(moNode);
						declarationsToSkip.add(entityName);
					}
				}
			}
		}
		
		if((completness.equals("complete"))||(completness.equals("substractive"))){
			for(int i=1,count=declarations.getMemberCount(); i<= count; i++){
				EEntity_declaration declaration = declarations.getByIndex(i);
				if(!((declaration instanceof EUsed_declaration)||(declaration instanceof EReferenced_declaration))){
					continue;
				}
				EEntity_definition definition = (EEntity_definition)declaration.getDefinition(null);
				// Skip complexes
				if(definition.getComplex(null)){
					continue;
				}
				String entityName = definition.getName(null);
				if(declarationsToSkip.contains(entityName)){
					System.err.println(" Skipping "+entityName);
					continue;
				}
				// System.err.println(i+"-th "+definition);
				putNameCoLink(key, isARM, entityNameToCCO, entityName);
				// System.err.println(definition+" "+entityNameToCCO.get(definition.getName(null)));			
			}
		}else if(completness.equals("selective")){
			NodeList moduleNodes = moduleNode.getChildNodes();
			for(int moIndex=0; moIndex < moduleNodes.getLength(); moIndex++){
				Node moNode = moduleNodes.item(moIndex);
				String nodeName = moNode.getNodeName();
				if(nodeName.equals("arm_entity")){
					if(isARM){
						String entityName = parseEntityName(moNode);
						putNameCoLink(key, isARM, entityNameToCCO, entityName);
					}
				}else if(nodeName.equals("mim_entity")){
					if(!isARM){
						String entityName = parseEntityName(moNode);
						putNameCoLink(key, isARM, entityNameToCCO, entityName);
					}
				}else if(!nodeName.equals("#text")){
					System.err.println("Unsupported node in module "+nodeName);
				}
			}
		}
		// Process entity
	}

	private static void putNameCoLink(Object key, boolean isARM, Map<String, List> entityNameToCCO, String entityName) throws SdaiException {
		putNameCoLink(key, entityName, entityNameToCCO);
		if(key instanceof ConformanceOption){
			ConformanceOption co = (ConformanceOption)key;
			if(isARM)
				co.addArmEntity(entityName);
			else
				co.addMimEntity(entityName);
		}
	}

	private static SdaiSession startSession() throws SdaiException {
		// General stuff later likely to be moved to other method
		SdaiSession session = SdaiSession.openSession();
		// For now process ARM only
		// General stuff
		session.startTransactionReadOnlyAccess();
		return session;
	}

	private static void putNameCoLink(Object key, String entityName, Map<String, List> EntityNameToCCO) throws SdaiException {
		List cco = EntityNameToCCO.get(entityName);
		// System.err.println(definition+" - "+entityNameToCCO.get(definition.getName(null))+" "+entityNameToCCO.size());
		if(cco == null){
			cco = new ArrayList();
			EntityNameToCCO.put(entityName, cco);
		}
		if(!cco.contains(key)){
			cco.add(key);
		}
	}

	private static void create_x_in_ccs(Document coDoc, Element parent, boolean isARM)throws SdaiException{
		// Finally create XML stuff
		Map<String, List> entityNameToCCO;
		if(isARM){
			entityNameToCCO = armEntityNameToCCO; 
		}else{
			entityNameToCCO = mimEntityNameToCCO;
		}
		Set keys = entityNameToCCO.keySet();
		// System.err.println(" SIZE "+keys.size());
		Iterator it = keys.iterator();
		while(it.hasNext()){
			Element entity_in_cc;
			if(isARM){
				entity_in_cc = coDoc.createElement("arm_entity_in_cc");
			}else{
				entity_in_cc = coDoc.createElement("mim_entity_in_cc");
			}
			parent.appendChild(entity_in_cc);
			String definition = (String)it.next();
			entity_in_cc.setAttribute("name", definition);
			List cco = entityNameToCCO.get(definition);
			for(int i=0,count=cco.size(); i<count; i++){
				Object currentId = cco.get(i);
				if(currentId instanceof ConformanceOption){
					ConformanceOption co = (ConformanceOption)currentId;
					Element co_id = coDoc.createElement("co_ref");
					co_id.setAttribute("name", co.getId());
					entity_in_cc.appendChild(co_id);
				}else if(currentId instanceof ConformanceClass){
					ConformanceClass cc = (ConformanceClass)currentId;
					Element cc_id = coDoc.createElement("cc_id");
					cc_id.setAttribute("id", cc.getId());
					entity_in_cc.appendChild(cc_id);
					// System.err.println(" TODO - implement cc_id ");
				}else{
					throw new SdaiException(SdaiException.ED_NVLD," Not valid member of map "+currentId);
				}
			}
		}
	}
*/	
}
