package OWLpreprocessing ;

import java.util.ArrayList ;
import java.util.HashMap ;
import java.util.Iterator;
import java.util.Map ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class OWL_CreaSubclassesAndDisjunctness 
{
	/**
	 *  common file handling methods
	 */
	private  OWL_BufferedWriterUtils bwu                     = new OWL_BufferedWriterUtils() ;
    private  int                 subclassCount           = 0 ;
    private  int                 disjointclassCount      = 0 ;
    private  int                 disjointclassGroupCount = 0 ;

	OWL_CreaSubclassesAndDisjunctness(String DBname,
			                          String OWLintendedClassStructure_filename,
			                          String OWLsubclasses_filename,
			                          String disjointOWLclasses_filename
			                         ) 
	{
        ArrayList<String[]> OWLsubclasses               = new ArrayList<String[]>() ; 
        ArrayList<ArrayList<String>> disjointOWLclasses = new ArrayList<ArrayList<String>>() ;  
        
        String indentedList = null ; 
        
        try {indentedList = classifyIndentedOWLclassStruct(OWLintendedClassStructure_filename) ; }  
        catch (IOException e)  { e.printStackTrace(); }
        
        /**
         *  the essence of class: generates both of them :)
         */
        processIntendentedList(indentedList,
                               OWLsubclasses,
                               disjointOWLclasses
                              ) ;

        writeOWLsubclasses(DBname,
        		           OWLsubclasses_filename,
        		           OWLsubclasses) ;
        
        writeDisjointOWLclasses(DBname,
        		                disjointOWLclasses_filename,
        		                disjointOWLclasses
        		               ) ;
        /**
         *  Logging of the result
         */
        bwu.log(new StringBuilder()
                    .append("    asserted  " + this.subclassCount      + " OWL subclass(es)\n")
                    .append("    asserted  " + this.disjointclassCount + " OWL disjoint class(es) " +
                                     "into " + this.disjointclassGroupCount + " group(s)\n")
                    .toString()
               ) ;

	}   //  end of constructor()

	
	private void insertSQLcmdOfClassSubclass(BufferedWriter bw,
			                                 String className, 
			                                 String superclassName)
	{
		String insertCmd = "INSERT INTO OWL_CLASSES " +
	                       "(class_IRI, superclass_of_IRI, class_individual_count, annotated)" ;
		String values    = "VALUES ('" + className + "', '" + superclassName + "', 0, TRUE) ;" ;

        try {bw.write(insertCmd + "\n" + values + "\n\n") ; }  
        catch (IOException e) { e.printStackTrace(); }
		
	}   //  end of method insertSQLcmdOfClassSubclass()

	
	private void insertSQLcmdOfDisjointClassGroup(BufferedWriter bw,
			                                      int disjointGroupId,
			                                      ArrayList<String> disjointOWLclasses)
	{
		String insertCmd = "INSERT INTO DISJOINT_OWL_CLASSES (owl_class_IRI, disjoint_group_id)" ;
		
    	Iterator<String> itr = disjointOWLclasses.iterator();
        while(itr.hasNext())
        {
 		    String values = "VALUES ('" + itr.next() + "', " + disjointGroupId + ") ;";

 	        try {bw.write(insertCmd + "\n" + values + "\n\n") ; }  
 	        catch (IOException e) { e.printStackTrace(); }
 	        
 	        this.disjointclassCount++ ;
        }

	}   //  end of method insertSQLcmdOfDisjointClassGroup()

	
    private void writeOWLsubclasses(String              DBname,
    		                        String              OWLsubclasses_filename,
                                    ArrayList<String[]> OWLsubclasses) 
    {
   		BufferedWriter bw = this.bwu.createBufferedWritablefile(OWLsubclasses_filename) ;
    	
    	writeSQLheader(DBname, bw, "SO_upload_04_owl_classes.sql") ;
        
        Iterator<String[]> itr = OWLsubclasses.iterator();
        while(itr.hasNext())
        {
            String [] subclassPair = itr.next();
            
            //                              class           superclass
            insertSQLcmdOfClassSubclass(bw, subclassPair[1], subclassPair[0]) ;  
            
            this.subclassCount++ ;
        }

    	this.bwu.writeSQLcommit(bw) ;
    	this.bwu.closeBufferedWritablefile(bw);
    	
    }   //  end of method writeOWLsubclasses() 


    
    private void writeDisjointOWLclasses(String                       DBname,
    		                             String                       disjointOWLclasses_filename,
    		                             ArrayList<ArrayList<String>> disjointOWLclasses
    		                            ) 
    {
   		BufferedWriter bw = this.bwu.createBufferedWritablefile(disjointOWLclasses_filename) ;
    	
    	writeSQLheader(DBname, bw, "SO_upload_05_disjoint_classes.sql") ;
    	
        Iterator<ArrayList<String>> itr = disjointOWLclasses.iterator();
        int disjointGroupId = 1 ;
        while(itr.hasNext())
        {
        	insertSQLcmdOfDisjointClassGroup(bw,
                                             disjointGroupId,
                                             itr.next()
                                            ) ;        	
        	disjointGroupId++ ;
        }

    	this.bwu.writeSQLcommit(bw) ;		
		this.bwu.closeBufferedWritablefile(bw) ;
    	
		this.disjointclassGroupCount = --disjointGroupId ;
		
		
    }   //  end of method writeDisjointOWLclasses()
    
    
    private void writeSQLheader(String         DBname,
    		                    BufferedWriter bw,
    		                    String         fileName
    		                   ) 
    {
    	ArrayList<String> hdr = new ArrayList<>();
		hdr.add("/*") ;
    	hdr.add("    Title:       " + fileName) ;
    	hdr.add("    Description: Generating OWL classes/subclasses in SQL format") ;
    	hdr.add("    Written by:  Java class " + this.getClass().getSimpleName()) ;
    	hdr.add("    Date:        " + this.bwu.getCurrTime()) ;
    	hdr.add(" */") ;
    	hdr.add("") ;
    	hdr.add("USE   " + DBname + " ;") ;
    	hdr.add("") ;
    	hdr.add("SET   TRANSACTION READ WRITE ;") ;
        hdr.add("START TRANSACTION ;") ;
    	hdr.add("") ;

    	this.bwu.writeBufferedWritablefile(bw, hdr) ;
    	
    }   //  end of method writeSQLheaderSubclasses()
    

	/**
     * Reads an indented list of OWL classes from a file and converts it into a 
     * numbered indented list. Each level of indentation is assigned a number, 
     * starting from 0.
     *
     * @param inputFilename The path to the input file containing the indented list
     * @return A string containing the numbered indented list
     * @throws IOException if the file cannot be read
     */
    private String classifyIndentedOWLclassStruct(String inputFilename) throws IOException 
    {
        String        content     = Files.readString(Paths.get(inputFilename));
        String        indentation = "    " ;
        String[]      lines       = content.split("\n");
        StringBuilder result      = new StringBuilder();
        
        for (String line : lines) 
        {
            if (line.trim().isEmpty()) 
            {
                result.append("\n");
                continue;
            }
            
            /**
             *  Count leading spaces to determine the level
             */
            int level = 0;
            while (line.startsWith(indentation)) 
            {
                level++;
                line = line.substring(4);
            }
            
            /**
             *  Add the level number and the class name
             */
            result.append(level).append(" ").append(line.trim()).append("\n");
            
        }   //  end of for/loop
        
        return result.toString();
    }
	

    /**
     *  Processes an indented list of Strings and generates two ArrayList outputs:
     *  
     *  1. OWLsubclasses: Contains parent-child relationships
     *  
     *  2. disjointOWLclasses: Contains groups of classes 
     *     at the same level under the same parent
     *
     *  @param indentedList The input string containing the indented list
     *  @param OWLsubclasses Output parameter for parent-child relationships
     *  @param disjointOWLclasses Output parameter for groups of classes at the same level
     */
    public void processIntendentedList(String                       indentedList,
                                       ArrayList<String[]>          OWLsubclasses,
                                       ArrayList<ArrayList<String>> disjointOWLclasses) 
    {
        /**
         *  Clear the output lists
         */
        OWLsubclasses.clear() ;
        disjointOWLclasses.clear() ;
        
        /**
         *  Split the input into lines and process each line
         */
        String[] lines = indentedList.split("\n") ;
        
        Map<Integer, String> levelToClass = new HashMap<>() ;
        Map<String, ArrayList<String>> parentToChildren = new HashMap<>() ;

        for (String line : lines) 
        {
            line = line.trim() ;
            if (line.isEmpty()) continue ;

            /**
             *  Extract level and class name using regex - now handles two digits
             */
            Pattern pattern = Pattern.compile("^(\\d{1,2})\\s+(.+)$") ;
            Matcher matcher = pattern.matcher(line) ;

            if (matcher.matches()) 
            {
                int level = Integer.parseInt(matcher.group(1)) ;
                String className = matcher.group(2).trim() ;

                int endOfClassNamePosition = className.indexOf(" ") ;
                if (endOfClassNamePosition > 0)
                {
                    className = className.substring(0, endOfClassNamePosition) ;
                }
                
                /**
                 *  Store the class for this level
                 */
                levelToClass.put(level, className) ;

                /**
                 *  If this is not the root level, create parent-child relationship
                 */
                if (level > 0) 
                {
                    String parentClass = levelToClass.get(level - 1) ;
                    if (parentClass != null) 
                    {
                        OWLsubclasses.add(new String[]{parentClass, className}) ;
                        /**
                         *  Track children for each parent
                         */  
                        parentToChildren.computeIfAbsent(parentClass, 
                                                         k -> new ArrayList<>()).add(className) ;
                    }
                }
                
            }   //  end of outer if
            
        }   //  end of for-loop

        /**
         *   Process siblings to create disjoint classes
         */
        for (ArrayList<String> siblings : parentToChildren.values()) 
        {
            if (siblings.size() > 1) 
            {
                disjointOWLclasses.add(new ArrayList<>(siblings)) ;
            }
        }
        
    }   //  end of method processIntendentedList()

}   //  end of class OWLclassPreprocessing
