package OWL2generator;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*; 

/**
 *  The <b><code>WriteClassStructure</code></b> class processes a tab-indented 
 *  text file (maintaining conformity with Protégé's requirements in this regard)
 *  and generates <code>OWL</code> code representing class hierarchies within a 
 *  Java framework for <code>OWL</code>/XML ontology creation. This class 
 *  contributes to the ontology generation process by establishing the class 
 *  structure, including inheritance relationships and potential disjointness 
 *  constraints.
 *  <p>
 *  <b>Key Functionalities:</b>
 *  <p>
 *  Reads Class Definitions: Parses a text file containing
 *  class names with indentation levels indicating their
 *  hierarchical relationships.
 *  <p>
 *  Identifies Subclasses: Analyzes the indentation structure
 *  to determine subclass relationships between classes.
 *  <p>
 *  Generates <code>OWL</code> Class Declarations: Creates <code>OWL</code> code
 *  snippets to declare all classes within the ontology.
 *  <p>
 *  Generates Subclass Axioms: Writes <code>OWL</code> code to define
 *  subclass relationships between identified classes.
 *  <p>
 *  Handles Disjoint Classes (Optional): Optionally generates
 *  <code>OWL</code> constructs to specify that certain classes are mutually
 *  exclusive (cannot have overlapping instances).
 *  <p>
 *  Handles File I/O: Manages file reading and writing
 *  operations.
 *  <p>
 *  <b>Environment:</b>
 *  <ul>
 *  <li>    IDE:              Eclipse IDE for Java Developers
 *  <li>    Version:          2021-12 (4.22.0)
 *  <li>    Build id:         20211202-1639
 *  <li>    HW Model Name:    iMac, MacOS Monterey, 12.5.1
 *  <li>    Processor Name:   Quad-Core Intel Core i5
 *  <li>    Processor Speed:  3.2 GHz
 *  <li>    Memory:           32 GB 1867 MHz DDR3
 *  <li>    Disk:             APPLE SSD SM0256G    
 *  <li>    Serial:           DGKRC080GG7V
 *  </ul>
 *  @version 1-001
 *  @since   2024/06/26
 *  @author  Edit Hlaszny (https://www.edithlaszny.eu/ edithlaszny@gmail.com)
 */

public class WriteClassStructure 
{
    private String         baseURI           = "" ;
	private boolean        classDisjunctness = false ;
    private int            countOfClasses    = 0 ;
    private int            countOfSubclasses = 0 ;
	private String         inFilePath        = "" ;
	private int            maxIndentation    =  0 ;   
	private String         outFilePath       = "" ;
    private OWL2_Generator OWL2G             = new OWL2_Generator() ;
	private FileWriter     writer            = null ;
	
	
    WriteClassStructure (String  tabIndentedClassStructure, 
    		             String  outFilePath,
                         String  baseURI,
    		             boolean eachClassIsDisjunct)
	{
		this.inFilePath        = tabIndentedClassStructure ;
		this.outFilePath       = outFilePath ;
		this.baseURI           = baseURI ;
		this.classDisjunctness = eachClassIsDisjunct ;
		
		writeContent() ;
		
        /**
         *  Logging of the output volume
         */
        OWL2G.log(new StringBuilder()
                      .append("    declared " + this.countOfClasses + " class(es)\n")
                      .append("    declared " + this.countOfSubclasses + " subclass(es)\n")
                      .toString()
                 ) ; 
		
	}   //  end of constructor
		
    
	void writeContent()
	{
	    try 
	    {
	        File file = new File(this.outFilePath) ;
	        
	    	// Append mode (set to false for overwrite)
	        this.writer = new FileWriter(file, true) ;
	        
	        writeXMLcontent() ;
            
        } catch (IOException e) 
	    {
	        System.err.println("Error appended to the file: " + e.getMessage()) ;
	        
	    } finally 
	    {
	        // Close the file (optional, as FileWriter closes automatically on close())
	        try 
	        {
	        	this.writer.close(); // Close the writer explicitly here (optional)closeFile(file) ;	            
	        } 
	        catch (IOException e) 
	        {
	            System.err.println("Error closing file: " + e.getMessage()) ;
	        }
	    }
		
	}   //  end of method writeContent() 
    
    
    public void writeXMLcontent() throws IOException 
    {
 	    ArrayList<ClassEntity> classList = new ArrayList<ClassEntity>();
 	    classList = readClassNames();
 	    
    	genClasses(        classList) ;
		genSubClasses(     classList) ;
		
		/**
		 *  automatic setting of class-disjunctness
		 *  causes a lot of reasoning inconsistencies:
		 *  These disjunctness should be set separatelly
		 */
		if (this.classDisjunctness == true)
		{
    	    genDisjunctClasses(classList) ;
		}
    	
    }   //  end of method writeXMLcontent()
  
    
    private int countIndentation(String line) 
    {
        int indentation = 0;
        
        for (char c : line.toCharArray()) 
        {
            if (c == '\t') 
            	indentation++ ;
            else
                break ;
        }
        
        if (this.maxIndentation < indentation)
        	this.maxIndentation = indentation ;
        
        return indentation ;
        
    }   //  end of method countIndentation

    
    private void genDisjunctClasses(ArrayList<ClassEntity> classList) throws IOException
    {
    	SharedUtils shu = new SharedUtils(this.baseURI) ;
    	
    	/**
    	 *  Beginning from the highest indentation
    	 */
    	for (int indentation  = this.maxIndentation ; 
    			 indentation >= 0 ;
    			 indentation--)    		
        {   //                 because of look-ahead: - 1              
    		for (int idx = 0 ; idx < classList.size() - 1 ; idx++)
    		{
    		    ClassEntity ce = classList.get(idx) ;
    	
    		    /**
    		     *  look-ahead condition
    		     */
    		    boolean thereIsMin2IdenticalCE = (indentation == classList.get(idx).level     && 
                                                  indentation == classList.get(idx + 1).level) ;
        		if (thereIsMin2IdenticalCE)
        		{
        			this.writer.write("    <DisjointClasses>\n");
            		    
            		int idxOfDJclasses  = idx ;
            		while (indentation == ce.level         && 
            		  	   idxOfDJclasses < classList.size())
            		{
            			/**
            			 *  current disjoint class name is written out
            			 */
            			this.writer.write("        " + shu.formatClassName(ce.name));
                		    
                		classList.remove(idxOfDJclasses) ;

                		/**
                		 *   Think about it: this checking IS NEEDED !
                		 */
                        if (idxOfDJclasses < classList.size())
                        {
                		    ce = classList.get(idxOfDJclasses) ;
                        }
            	    }
              		    	 
            		this.writer.write("    </DisjointClasses>\n");
    		    }
        		
    		}   //  end of inner loop

    		/** 
    		 *  The single classes belonging to this indentation should be removed
    		 */
    		for (int idx = 0 ; idx < classList.size() - 1 ; idx++)
    		{
    			ClassEntity ce = classList.get(idx) ;
    			if (ce.level == indentation)
    			{
    				classList.remove(idx) ;
    			}
    			
    		}   //  end of inner loop
    		
        }   //  end of outer loop

	    /**
	     *  The debris has already been removed, and what remains  
	     *  are the highest indented classes and the lonely ones
	     */
		ArrayList<ClassEntity> remained = new ArrayList<ClassEntity>() ;

	    /**
	     *  Collecting the highest indented classes
	     */
		classList.stream().filter(ce -> ce.level == 0)
                          .forEach(ce -> remained.add(ce));	        
		
		if (remained.size() > 1)
		{
			this.writer.write("    <DisjointClasses>\n");

			remained.forEach(ce -> {   try 
			                           {
				                           this.writer.write("        " + ce.name + "\n") ;
			                           }
	                                   catch (IOException e) 
		                               {
		                                   System.err.println("Error creating or writing to file: " + 
		                                                      e.getMessage()) ;
		                               }			
				                   }
			                ) ;
	        
	        this.writer.write("    </DisjointClasses>\n");
		}
    	
    }   //  end of method genDisjunctClasses()

    
    private void genClasses(ArrayList<ClassEntity> classList) throws IOException
    {
    	SharedUtils obc = new SharedUtils(this.baseURI) ;

    	for (Iterator<ClassEntity> iterator = classList.iterator() ;
        		                   iterator.hasNext() == true ;
            )
        {
        	ClassEntity ce = iterator.next() ;  
        	this.writer.write(obc.declareClass(ce.name));
        	
        	this.countOfClasses++ ;
        }
    	
    }   //  end of method genClasses()
    
    
    private void genSubClasses(ArrayList<ClassEntity> classList
    		                  ) throws IOException
    {
        /** 
         *  The algorithm was taken from here:
         *  https://stackoverflow.com/questions/21735468/parse-indented-text-tree-in-java
         */
    	SharedUtils obc   = new SharedUtils(this.baseURI) ;
    	Stack<ClassEntity> stack = new Stack<ClassEntity>() ; 

    	ClassEntity ce1 = new ClassEntity();
    	ClassEntity ce2 = new ClassEntity() ;

    	stack.push( ce1) ;
    	
        for (Iterator<ClassEntity> iterator = classList.iterator() ;
        		                   iterator.hasNext() == true ;
            )
        {
        	ce1 = stack.peek() ;
        	ce2 = iterator.next() ;
        	
        	if (ce1.level < ce2.level)
        	{
        		if (ce2.level > 0)
        		{   //                                    superclass  subclass
        		    this.writer.write(obc.declareSubClass(ce1.name,   ce2.name)) ;
        		    this.countOfSubclasses++ ;
        		}
        		
        		stack.push(ce2) ;
        	}
        	else
        	{
        		while (ce1.level >= ce2.level  && stack.size() >=2) 
        		{
        			stack.pop() ;
        			ce1 = stack.peek() ;
        		}

        		
        		if (ce2.level > 0)
        		{   //                                    superclass  subclass
        		    this.writer.write(obc.declareSubClass(ce1.name,   ce2.name)) ;
                    this.countOfSubclasses++ ;
        		}

        		stack.push(ce2) ;
        	}
        }
    	
    }   //  end of method genSubClasses()
    
    
    private ArrayList<ClassEntity> readClassNames() throws IOException  
    {
    	ArrayList<ClassEntity> classList = new ArrayList<ClassEntity>();
	  
        try (BufferedReader reader = new BufferedReader(new FileReader(this.inFilePath))) 
        {
            String line ;
        
            while ((line = reader.readLine()) != null) 
            {
                if (line.isEmpty()) 
                	continue ;

                classList.add(new ClassEntity(countIndentation(line), line.trim()));

            }   //  end of while
            
        }
        catch (IOException e) 
        {
            System.err.println("Error opening or reading the file: " + e.getMessage()) ;
        } 

    	return classList ;
    	
    }   //  end of method readClassNames()
    
    
    private class ClassEntity
    {
        public String name  ;
        public int    level ;
        
        ClassEntity(int    level, 
                    String name)
        {
            this.level = level ;
            this.name  = name ;
    
        }   //  end of constructor-1
        
        
        ClassEntity()
        {
            ;
            
        }   //  end of constructor-2
        
    }   //  end of class ClassEntity
    
}   //  end of class WriteClassStructure
