/*
 * Copyright (C) 1999-2006 <a href="mailto:paschke@in.tum.de">Adrian Paschke</a>
 * 
 * The OWL2Prova library is part of the Rule Based Service Level Agreement (RBSLA)
 * framework, available at 
 * <a href=" http://ibis.in.tum.de/staff/paschke/rbsla/index.htm">RBSLA</a>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package rbsla.owl.converter.dlp;

import java.util.Map;
import rbsla.owl.NameSpaceList;
import rbsla.owl.ProvaModel;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.impl.ResourceImpl;

/**
 * Map the OWL instances to individual keys according their given relations
 * in the owl file (sameAs, differentForm, etc.). 
 * Also build up the subClass graph
 * and map it to Prova syntax and map anons to anon ids.
 * 
 * @version 0.1 <1 Feb 2005>
 * @since 0.1
 */
public class IdentifyMapper {
	
	private int start_id=10000; // start id for instances
	
	private boolean anonMapping; // map anons
	private boolean instanceMapping; // map equivalent instances
	private boolean classMapping; //map equivalent classes
	
	protected Model JenaModel;
	protected IndividualList IndiList;
	protected IndividualList CompositeList;
	protected IndividualList ClassList;
	protected IndividualList GroupList;
	protected IndividualList IdList;
	protected MapperList MapList;
	protected NameSpaceList NSL;


	/**
	 * Constructor
	 * @param _mapEnable sets individual mapping "enable"
	 */
	
	public IdentifyMapper() {
		anonMapping=true; // map anons to anon ids
		instanceMapping=true; // map equivalent instances to one unique id
		classMapping=true; // map equivalent classes
		
		IndiList = new IndividualList();
		CompositeList = new IndividualList();
		ClassList = new IndividualList();
		GroupList = new IndividualList();
		IdList = new IndividualList();
		MapList = new MapperList();
	}
	
	/**
	 * Constructor
	 * @param _IndiList IndiList of further mapping
	 * @param _CompositeList CompositeList of further mapping
	 * @param _ClassList ClassList of further mapping
	 * @param _GroupList GroupList of further mapping
	 * @param _IdList IdList of further mapping
	 * @param _MapList Mapping List of further mapping
	 */
	public IdentifyMapper  (IndividualList _IndiList,
							IndividualList _CompositeList,
							IndividualList _ClassList,
							IndividualList _GroupList,
							IndividualList _IdList,
							MapperList _MapList){
		anonMapping=true; // map anons to anon ids
		instanceMapping=true; // map equivalent instances to one unique id
		classMapping=true; // map equivalent classes
		
		IndiList = _IndiList;
		CompositeList = _CompositeList;
		ClassList = _ClassList;
		GroupList = _GroupList;
		IdList = _IdList;
		MapList = _MapList;	
	}
	
	/**
	 * Map anons to an unique anon_id
	 * @param mapping
	 */
	public void setAnonMapping(boolean mapping) {
		anonMapping = mapping;
	}
	
	/**
	 * Map equivalent instances to an unique identifier (ID)
	 * @param mapping
	 */
	public void setInstanceMapping(boolean mapping) {
		instanceMapping = mapping;
	}
	
	/**
	 * Map equivalent classes to each other
	 * @param mapping
	 */
	public void setClassMapping (boolean mapping) {
		classMapping = mapping;
	}
	
	/**
	 * Starts and controles the Individual Mapping process
	 * @param _JenaModel the Jena Model of the OWL description
	 * @param _writer the StringWriter
	 * @return ProvaModel
	 */
	public ProvaModel run(Model _JenaModel, ProvaModel _writer){
		
		
		// create namespace list						 		
		NSL = new NameSpaceList(_JenaModel); // namespace list
		
		//Phase 1: Collect the  instances, classes and anons in the respective lists
		if ((anonMapping) || (classMapping) || (instanceMapping))
		for (StmtIterator iter = _JenaModel.listStatements((Resource) null,_JenaModel.createProperty("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"),(RDFNode) null); iter.hasNext();){
			Statement stmt = iter.nextStatement();
			if ((classMapping) || (instanceMapping)) this.collectInstances_and_Classes(stmt); // collect instances and classes
			if (anonMapping) this.collectAnons(stmt); // collect anons
		}
				
		//Phase 2: take the differentForm predicates into account
		if ((classMapping) || (instanceMapping)) {
			
		for (StmtIterator iter = _JenaModel.listStatements((Resource) null,_JenaModel.createProperty("http://www.w3.org/2002/07/owl#differentFrom"),(RDFNode) null); iter.hasNext();){
			Statement stmt = iter.nextStatement();
			this.selectDIFFERENTFORM(stmt);
		}
					
		//Phase 3: take the sameAs predicates into account
		for (StmtIterator iter = _JenaModel.listStatements((Resource) null,_JenaModel.createProperty("http://www.w3.org/2002/07/owl#sameAs"),(RDFNode) null); iter.hasNext();){
			Statement stmt = iter.nextStatement();
			this.selectSAMEAS(stmt);
		}
		
		//Phase 3.1: take the equivalentClass predicates into account
		for (StmtIterator iter = _JenaModel.listStatements((Resource) null,_JenaModel.createProperty("http://www.w3.org/2002/07/owl#equivalentClass"),(RDFNode) null); iter.hasNext();){
			Statement stmt = iter.nextStatement();
			this.selectSAMEAS(stmt);
		}
		
		//Phase 3.2: take the sameIndividualAs predicates into account
		for (StmtIterator iter = _JenaModel.listStatements((Resource) null,_JenaModel.createProperty("http://www.w3.org/2002/07/owl#sameIndividualAs"),(RDFNode) null); iter.hasNext();){
			Statement stmt = iter.nextStatement();
			this.selectSAMEAS(stmt);
		}}
				
		//Phase 4: Create a collection of non categorizable instances
		this.createComposites();
		
		//Phase 5: Mapping of the instances to individuals and adding the mapping to the _writer
		if (instanceMapping) _writer = this.mappingIndividuals(_writer, start_id); // map instances
		if (classMapping) _writer = this.mappingClasses(_writer); // map classes
		if (anonMapping) this.mappingAnons(); // map anons
		
		return _writer;
	}

	/**
	 * Add anons to the anon ID List
	 * @param stmt a JENA statement.
	 */
	
	public void collectAnons (Statement stmt){
		if (stmt.getSubject().isAnon()){ // subject is anon
			if (!(IdList.isElementOfList(stmt.getSubject().getId().toString()))){
				IdList.addIndi(stmt.getSubject().getId().toString());
			}
		}
		if (stmt.getObject() instanceof ResourceImpl){
			ResourceImpl obj = (ResourceImpl) stmt.getObject();
			if (obj.isAnon()){ // object is anon
				if (!(IdList.isElementOfList(obj.getId().toString()))){
					IdList.addIndi(obj.getId().toString());
				}
			}
		}
	}
	
	/**
	 * Add instances  and class names to the IndiList/ClassList
	 * @param stmt a JENA Statement
	 */
	
	public void collectInstances_and_Classes (Statement stmt){
			if (!stmt.getSubject().isAnon())
			if (stmt.getObject() instanceof ResourceImpl) {
				ResourceImpl Obj = (ResourceImpl) stmt.getObject();
				
				/*
				 * Collect the individuals
				 * Only add user-defined individuals
				 */				
				if (!(NSL.getAbbreviation(Obj.getNameSpace()).equals("rdfs_")
						|| NSL.getAbbreviation(Obj.getNameSpace()).equals("rdf_")
						|| NSL.getAbbreviation(Obj.getNameSpace()).equals("owl_") )){
										
					String Subject = NSL.getAbbreviation(stmt.getSubject().getNameSpace()) 
									 + stmt.getSubject().getLocalName(); // prefix_individual
					if (!(IndiList.isElementOfList(Subject))) { // add new individual
						IndiList.addIndi(Subject);
					}
				}
				
				/*
				 * Collect the classes
				 * [namespace#classname, 
				 * http://www.w3.org/1999/02/22-rdf-syntax-ns#type, 
				 * http://www.w3.org/2000/01/rdf-schema#Class]
				 */
				if(NSL.getAbbreviation(Obj.getNameSpace()).equals("rdfs_")
						&& Obj.getLocalName().equals("Class")){
					String ClassName = NSL.getAbbreviation(stmt.getSubject().getNameSpace()) + 
									   stmt.getSubject().getLocalName(); //prefix_class
					if (!(ClassList.isElementOfList(ClassName))){ // add new class
						ClassList.addIndi(ClassName);
					}
				}
			}		
	}
	
	/**
	 * Evaluate the "differentForm" Statements
	 * @param Statement
	 */
	
	public void selectDIFFERENTFORM(Statement stmt){
			if (stmt.getObject() instanceof ResourceImpl){
				ResourceImpl Obj = (ResourceImpl) stmt.getObject();
				String Subject = NSL.getAbbreviation(stmt.getSubject().getNameSpace()) 
								+ stmt.getSubject().getLocalName();
				String Object = NSL.getAbbreviation(Obj.getNameSpace()) +
								Obj.getLocalName();
				// 1.case: Both elements of the differentForm statement are included in the individual list 
				if (IndiList.isElementOfList(Subject) && IndiList.isElementOfList(Object)){
					CompositeList.addIndiList(new IndividualList(Subject));
					IndiList.removeIndi(Subject);
					CompositeList.addIndiList(new IndividualList(Object));
					IndiList.removeIndi(Object);
				}
				// 2. case: one element in individual list, other one in composite list
				if (IndiList.isElementOfList(Subject) && CompositeList.isElementOfList(Object)){
					CompositeList.addIndiList(new IndividualList(Subject));
					IndiList.removeIndi(Subject);
				}
				if (IndiList.isElementOfList(Object) && CompositeList.isElementOfList(Subject)){
					CompositeList.addIndiList(new IndividualList(Object));
					IndiList.removeIndi(Object);
				}
				
			}	
	}
	
	/**
	 * Evaluate the "sameAs" Statements
	 * @param Statement
	 */
	
	private void selectSAMEAS(Statement stmt){
			if (stmt.getObject() instanceof ResourceImpl){
				ResourceImpl Obj = (ResourceImpl) stmt.getObject();
				if (Obj.isAnon()) return;
				String Subject = NSL.getAbbreviation(stmt.getSubject().getNameSpace()) +
								 stmt.getSubject().getLocalName();
				String Object = NSL.getAbbreviation(Obj.getNameSpace()) +
								Obj.getLocalName();
				// 1.1. case: both elements of the sameAs statement are included in the individual list
				if (IndiList.isElementOfList(Subject) && IndiList.isElementOfList(Object)){
					IndividualList tmp = new IndividualList(Subject);
					tmp.addIndi(Object);
					CompositeList.addIndiList(tmp);
					IndiList.removeIndi(Subject);
					IndiList.removeIndi(Object);
				}
				
				// 1.2. case: both elements of the sameAs statement are included in the class list (class equivalence)
				if (ClassList.isElementOfList(Subject) && ClassList.isElementOfList(Object)){
					IndividualList tmp = new IndividualList(Subject);
					tmp.addIndi(Object);
					GroupList.addIndiList(tmp);
					ClassList.removeIndi(Subject);
					ClassList.removeIndi(Object);
				}
				
				// 2.1. case: one element in individual list, other one in composite list
				if (IndiList.isElementOfList(Subject) && CompositeList.isElementOfList(Object)){
					CompositeList.phase3add(Subject, Object);
					IndiList.removeIndi(Subject);
				}
				if (IndiList.isElementOfList(Object) && CompositeList.isElementOfList(Subject)){
					CompositeList.phase3add(Object, Subject);
					IndiList.removeIndi(Object);
				}
				
				//	 2.2. case: one element in class list, other one in group list
				if (ClassList.isElementOfList(Subject) && GroupList.isElementOfList(Object)){
					GroupList.phase3add(Subject, Object);
					ClassList.removeIndi(Subject);
				}
				if (ClassList.isElementOfList(Object) && GroupList.isElementOfList(Subject)){
					GroupList.phase3add(Object, Subject);
					ClassList.removeIndi(Object);
				}
				
				//	3.1. case: both elements in composite list
				if (CompositeList.isElementOfList(Subject) && CompositeList.isElementOfList(Object)){
					if (!(CompositeList.getIndividualListOfElement(Subject).equals(
							CompositeList.getIndividualListOfElement(Object)))){
						CompositeList.mergeIndividualLists(Subject, Object);
					}
				}
				
				//	3.2. case: both elements in group list
				if (GroupList.isElementOfList(Subject) && GroupList.isElementOfList(Object)){
					if (!(GroupList.getIndividualListOfElement(Subject).equals(
							GroupList.getIndividualListOfElement(Object)))){
						GroupList.mergeIndividualLists(Subject, Object);
					}
				}
				
			}		
	}
	
	/**
	 * Add a Element of IndiList to the Composition list
	 */
	
	public void createComposites(){
		while (!IndiList.isEmpty()){
			if (IndiList.isString(0)){
				CompositeList.addIndiList(new IndividualList(IndiList.removeFirstIndi()));
			}else{
				CompositeList.addIndiList(IndiList.removeFirstIndiList());
			}
		}
	}
	
	/**
	 * Maps individuals to its explicit identifier
	 * @param _writer ProvaModel
	 * @param HashStart Value of the starting Hash Value
	 * @return ProvaModel
	 */
	
	public ProvaModel mappingIndividuals(ProvaModel _writer, int HashStart){
		int i = HashStart;
		for (int j = 0; j < CompositeList.sizeOfList(); j++){
			i++;
			MapList.add(new MapperElement(CompositeList.getIndiList(j), i)); // add individual ids to map list
			for (int k = 0; k < CompositeList.getIndiList(j).sizeOfList(); k++){
				
				// mapping(id, individual)
				
				_writer.write("mapping(\"" + i + "\", \"" + 
						CompositeList.getIndiList(j).getIndi(k) + "\")." + "\n");
			
			}
		}
		return _writer;
	}
	
	/**
	 *  Add a Relation of IDlist (anon ids) and Hash Value to the MapList
	 */
	
	public void mappingAnons(){
		// Anons mapping
		int i = 0;
		for (int j = 0; j < IdList.sizeOfList(); j++){
			i++;
			MapList.add( //add anon ids to map list
					new MapperElement(new IndividualList(IdList.getIndi(j)),i));	
		}
	}
	
	/**
	 * Writes the Class group lists to the logical form in PROVA syntax
	 * @param _writer ProvaModel
	 * @return ProvaModel
	 */
	
	public ProvaModel mappingClasses(ProvaModel _writer){
		for (int i = 0; i < GroupList.sizeOfList(); i++){
			IndividualList tmp = GroupList.getIndiList(i);
			for (int j = 0; j < tmp.sizeOfList(); j++){
				for (int k = j+1; k < tmp.sizeOfList(); k++){
					
					// C(X) :- fact(D(X))
					// D(X) :- fact(C(X))
					// TODO not fact syntax
					
					_writer.write(tmp.getIndi(j) + "(X) :- fact(" +
							tmp.getIndi(k) + "(X))." + "\n");
					_writer.write(tmp.getIndi(k) + "(X) :- fact(" +
							tmp.getIndi(j) + "(X))." + "\n");
				}
			}
		}
		return _writer;
	}
	
	public MapperList getMapList(){
		return MapList;
	}
	
	public IndividualList getIndiList(){
		return IndiList;
	}
	
	public IndividualList getCompositeList(){
		return CompositeList;
	}
	
	public IndividualList getClassList(){
		return ClassList;
	}
	
	public IndividualList getGroupList(){
		return GroupList;
	}
	
	public IndividualList getIdList(){
		return IdList;
	}
	
}
