package OWLpreprocessing ;

import java.io.* ;
import java.util.* ;
import java.util.regex.* ;

import java.sql.Connection;

public class OWL_RelationEvaluator 
{
    /**
     *  Common file handling methods
     */
    private OWL_BufferedWriterUtils   bwu = new OWL_BufferedWriterUtils() ;
    private Connection  DBconnection      = null ;
    private OWL_DButils dbu               = new OWL_DButils() ;
    
    private GenerateNewIndividualName gni = null ;
    
    private String      errMsgFiller      = "    " ; 
    private String      fatalErrorMsg     = "" ;        //  cummulated errpr messages           
    private int         fatalErrorCount   = 0 ;         //  counter of fatal error messages           
    private int         itemCount         = 0 ;         //  processed data volume
    private int         n_ary_relation_id = 1 ;         //  autoincrement at INSERT N_ARY_RELATIONS
    private int         participant_id    = 1 ;         //  autoincrement at INSERT N_ARY_PARTICIPANTS
    private String      ANNOTATION        = "ANNOTATION" ;
    private String      NAME              = "NAME"       ;
    private String      PREDICATE         = "PREDICATE"  ;
    private String      OBJECT            = "OBJECT"     ; //  role_type in N_ARY_PARTICIPANTS
    private String      SUBJECT           = "SUBJECT"    ; //  role_type in N_ARY_PARTICIPANTS
    
    OWL_RelationEvaluator(Connection DBconnection ,
                          String     DBname ,
                          String     relationDefs_TXT ,
                          String     n_ary_relations_SQL ,
                          String     n_ary_participants_SQL ,
                          String     n_ary_data_assertions_SQL
                         ) 
    {
        this.DBconnection = DBconnection ;
        this.gni          = new GenerateNewIndividualName(DBconnection) ;
        
    	//  gni.__individualTest() ;

    	writeSQLfileHdr(n_ary_relations_SQL,       "n-ary relations",       DBname) ; 
        writeSQLfileHdr(n_ary_participants_SQL,    "n-ary participants",    DBname) ;
        writeSQLfileHdr(n_ary_data_assertions_SQL, "n-ary data assertions", DBname) ;
        
        processRelationDefs(relationDefs_TXT ,
                         n_ary_relations_SQL ,
                         n_ary_participants_SQL ,
                         n_ary_data_assertions_SQL
                         ) ;

        commitAndClose(n_ary_relations_SQL      ) ; 
        commitAndClose(n_ary_participants_SQL   ) ;
        commitAndClose(n_ary_data_assertions_SQL) ;
        
        if (this.fatalErrorMsg.length() > 0)
            dsplyFatalMsgAndExit( this.fatalErrorCount
                                  + " Fatal error(s) detected in "
                                  + this.getClass().getSimpleName() + ":\n"
                                  + this.fatalErrorMsg 
                                  + "Cannot continue. Do not update the DB with this data set unless you have\n"
                                  + "corrected the above error(s) in the file ./input/relationDefinitions.txt." ,  
                                 -1  //  fatal error exit code
                                ) ;
        
        
        /**
         *  Logging of the result
         */
        bwu.log(new StringBuilder()
                    .append("    asserted  " + this.itemCount + " n-ary causal relation(s)\n")
                    .toString()
               ) ;
        
    }   //  end of constructor
    
    private void processRelationDefs(String inFileName ,
                                     String n_ary_relations_SQL ,
                                     String n_ary_participants_SQL ,
                                     String n_ary_data_assertions_SQL
                                    )
    {
        ListIterator<TYPE_OF_RELATION> itr = evalRelationDefs(inFileName).listIterator();            
        while (itr.hasNext())
        {
            TYPE_OF_RELATION r = itr.next() ; 
 
            writeRowInto_N_ARY_RELATIONS(n_ary_relations_SQL, r) ;

            ListIterator<RelationMemberData> sub_itr = r.subject.listIterator();
            while (sub_itr.hasNext())
            {
                RelationMemberData s = sub_itr.next();
                
                writeRowInto_N_ARY_PARTICIPANTS(n_ary_participants_SQL,
                                                n_ary_data_assertions_SQL,
                		                        s, 
                		                        this.SUBJECT) ;
                this.participant_id++ ;
                
            }   //  end of while loop  traversing SUBJECTS      
            
            ListIterator<RelationMemberData> obj_itr = r.object.listIterator();
            while (obj_itr.hasNext())
            {
                RelationMemberData o = obj_itr.next();
                
                writeRowInto_N_ARY_PARTICIPANTS(n_ary_participants_SQL, 
                                                n_ary_data_assertions_SQL,
                		                        o, 
                		                        this.OBJECT) ;
                this.participant_id++ ;
            
            }   //  end of while loop traversing OBJECTS      
            
            this.n_ary_relation_id++ ;
        
            this.itemCount++ ;  // must not mixed with other vars :) 
            
        }   //  end of while loop
        
    }   //  end of method processRelationDefs() 
    
    private ArrayList<TYPE_OF_RELATION> evalRelationDefs(String inFileName) 
    {
        ArrayList<TYPE_OF_RELATION> relations = new ArrayList<>() ;
        
        try (BufferedReader reader = new BufferedReader(new FileReader(inFileName))) 
        {
            String line ;
            TYPE_OF_RELATION currentRelation = null ;
            
            while ((line = reader.readLine()) != null) 
            {
                line = line.trim() ;
                
                // Skip comments, empty lines, and lines with only spaces
                if (line.isEmpty() || line.startsWith("#")) 
                {
                    continue ;
                }
                
                // Check for PREDICATE line
                if (line.startsWith(this.PREDICATE)) 
                {
                    // Save previous relation if exists
                    if (currentRelation != null) 
                    {
                        relations.add(currentRelation) ;
                    }
                    
                    // Start new relation
                    currentRelation = new TYPE_OF_RELATION() ;
                    currentRelation.predicate = extractPredicate(line) ;

                    currentRelation.relationName = extractRelationName(line) ;
                    
                    currentRelation.relationAnnotation = extractRelationAnnotation(line) ;
                    
                    currentRelation.subject = new ArrayList<>() ;
                    currentRelation.object = new ArrayList<>() ;
                }
                
                else if (line.startsWith(this.NAME)) 
                {
                    currentRelation.relationName = extractRelationName(line) ;
                }

                else if (line.startsWith(this.ANNOTATION)) 
                {
                    currentRelation.relationAnnotation = extractRelationAnnotation(line) ;
                }
                
                // Check for SUBJECT line (including SUBJECT_0)
                else if (line.startsWith(this.SUBJECT)) {
                    if (currentRelation != null) {
                        RelationMemberData memberData = parseRelationMemberData(line);
                        if (memberData != null) {
                            currentRelation.subject.add(memberData);
                        }
                    }
                }
                
                // Check for OBJECT line (including OBJECT_0)
                else if (line.startsWith(this.OBJECT)) {
                    if (currentRelation != null) {
                        RelationMemberData memberData = parseRelationMemberData(line);
                        if (memberData != null) {
                            currentRelation.object.add(memberData);
                        }
                    }
                }
                
            }   //  end of while-loop
            
            // Don't forget the last relation
            if (currentRelation != null) 
            {
                relations.add(currentRelation) ;
            }
        } 
        catch (IOException e) 
        {
            System.err.println("Error reading file: " + e.getMessage()) ;
            e.printStackTrace() ;
        }
        
        return relations ;
        
    }   //  end of method evalRelationDefs() 
    
    private String extractPredicate(String line) 
    {
        // Pattern: PREDICATE = predicateName
        Pattern pattern = Pattern.compile("PREDICATE\\s*=\\s*(.+)") ;
        Matcher matcher = pattern.matcher(line) ;
        
        if (matcher.find()) 
        {
            return matcher.group(1).trim() ;
        }
        
        return "" ;
        
    }   //  end of method extractPredicate()

    private String extractRelationName(String line) 
    {
        // Pattern: NAME = relation name
        Pattern pattern = Pattern.compile("NAME\\s*=\\s*(.+)") ;
        Matcher matcher = pattern.matcher(line) ;
        
        if (matcher.find()) 
        {
            return matcher.group(1).trim() ;
        }
        
        return "" ;
        
    }   //  end of method extractRelationName()
    
    private String extractRelationAnnotation(String line) 
    {
        // Pattern: ANNOTATION = relation annotation
        Pattern pattern = Pattern.compile("ANNOTATION\\s*=\\s*(.+)") ;
        Matcher matcher = pattern.matcher(line) ;
        
        if (matcher.find()) 
        {
            return matcher.group(1).trim() ;
        }
        
        return "" ;
        
    }   //  end of method extractRelationAnnotation()
    
    private RelationMemberData parseRelationMemberData(String line) 
    {
        Pattern pattern = Pattern.compile("(SUBJECT|OBJECT)(_0)?\\s*=\\s*(\\S+)\\s*(\\S+)?\\s*(\\S+)?");
        Matcher matcher = pattern.matcher(line);
        
        if (matcher.find()) {
            RelationMemberData memberData = new RelationMemberData();
            memberData.OWLclassName = matcher.group(3).trim();
            
            // Check if data property name exists (group 4)
            if (matcher.group(4) != null && !matcher.group(4).trim().isEmpty()) {
                memberData.dataPropertyName = matcher.group(4).trim();
            }
            
            // Check if data property value exists (group 5)
            if (matcher.group(5) != null && !matcher.group(5).trim().isEmpty()) {
                memberData.dataPropertyValue = matcher.group(5).trim();
            }
            else
            {
                if (memberData.dataPropertyName  != null &&
                	memberData.dataPropertyValue == null
                   )
                {
                    this.fatalErrorMsg +=   this.errMsgFiller 
                            + "Data property " + memberData.dataPropertyName + " must have value.\n\n" ;
                	this.fatalErrorCount++ ;
                }
            }
            
            return memberData;
        }
        
        return null;
        
    }   //  end of method parseRelationMemberData()    

    private void writeSQLfileHdr(String outFile,
                                 String description,
                                 String DBname
                                )
    {
        String  content = 
                "/*\n" +          
                "    Title:       " + outFile + "\n" +         
                "    Description: " + description + "\n" +         
                "    Format:      OWL/XML encoding" + "\n" +         
                "    Written by:  Java class " + this.getClass().getSimpleName() + "\n" +         
                "    Date:        " + bwu.getCurrTime() + "\n" +         
                " */ \n" +          
                "USE   " + DBname + " ; \n" +         
                "\n" +          
                "SET   TRANSACTION READ WRITE ; \n" +         
                "START TRANSACTION ; \n" ;         

        //                              create mode
        addSQLcontent(outFile, content, false) ;
        
    }   //  end of method writeSQLfileHdr()
    
    private void commitAndClose(String outFile)
    {
        String  content = "\nCOMMIT ; \n" ;         

        //                              append mode
        addSQLcontent(outFile, content, true) ;
        
    }   //  end of method commitAndClose()
    
    private void addSQLcontent(String SQLfile, String content, boolean appendMode)
    {
        try  
        {
            FileWriter fw = new FileWriter(new File(SQLfile), appendMode) ;

            fw.write(content) ;
            fw.flush() ;
            fw.close() ;
        }
        catch (IOException e) { e.printStackTrace() ; }
        
    }   //  end of method addSQLcontent()

    private void writeRowInto_N_ARY_RELATIONS(String           n_ary_relations_SQL, 
    		                                  TYPE_OF_RELATION r) 
    {
        chkRelationData(r) ;
    	
        /**
         *  The named individuals belong to the class Collective_Causal_Event,
         *  but their names will be causal_Event_ID000nnn, which names better 
         *  express their meaning and role.
         */
    	String ccEvent = gni.getNewIndividualName("Collective_Causal_Event") ;

        String insertCmd =   "\nINSERT INTO N_ARY_RELATIONS " 
        		           + "(predicate_IRI, relation_name, relation_annotation, causal_event_individual) \n" 
        		           + "VALUES ('" 
        		           + r.predicate          + "',\n        '"     // field-1  
        		           + r.relationName       + "',\n        '"     // field-2  
        		           + r.relationAnnotation + "',\n        '"     // field-3
        		           + ccEvent              + "') ;\n" ;          // field-4

        //                                         // append mode
        addSQLcontent(n_ary_relations_SQL, insertCmd, true) ; 
    	
    }   //  end of method writeRowInto_N_ARY_RELATIONS()
    
    private void writeRowInto_N_ARY_PARTICIPANTS(String             n_ary_participants_SQL, 
    		                                     String             n_ary_data_assertions_SQL,
    		                                     RelationMemberData rmd,
    		                                     String             roleType)
    {
    	chkParticipantData(roleType, rmd) ;

    	String individual_name = gni.getNewIndividualName(rmd.OWLclassName) ;
    	
        String insertCmd =   "\nINSERT INTO N_ARY_PARTICIPANTS " 
		           + "(n_ary_relation_id, individual_name, class_IRI, role_type) \nVALUES ('" 
		           + this.n_ary_relation_id + "',\n        '" // field-1  
		           + individual_name        + "',\n        '" // field-2
		           + rmd.OWLclassName       + "',\n        '" // field-3
		           + roleType               + "') ;\n" ;      // field-4

        //                                            // append mode
        addSQLcontent(n_ary_participants_SQL, insertCmd, true) ; 
        
        if (rmd.dataPropertyName != null || rmd.dataPropertyValue != null)
        {
            insertCmd =   "\nINSERT INTO N_ARY_DATA_ASSERTIONS " 
 		           + "(participant_id, data_property_IRI, data_property_value) \nVALUES ('" 
 		           + this.participant_id   + "',\n        '" // field-1  
 		           + rmd.dataPropertyName  + "',\n        '" // field-2
 		           + rmd.dataPropertyValue + "') ;\n" ;      // field-3

             //                                               // append mode
             addSQLcontent(n_ary_data_assertions_SQL, insertCmd, true) ; 
        }
        
    }   //  end of method writeRowInto_N_ARY_PARTICIPANTS()
    
    private void chkRelationData(TYPE_OF_RELATION r)
    {
    	//  __test_ShowRelationData(r) ;
    	
       /**
        *  terminology: predicate is the maiden nem of object property :)
        */
    	if (! dbu.chkObjectPropertyValidity(this.DBconnection, r.predicate))
    	{
            this.fatalErrorMsg +=   this.errMsgFiller 
                                  + "Unknown predicate (" + r.predicate + ").\n\n" ;
        	this.fatalErrorCount++ ;
    	}

    }   //  end of method chkRelationData()
    
    private void chkParticipantData(String roleType, RelationMemberData role_type) 
    {
    	//  __test_ShowParticipantData(roleType, role_type) ;

    	if (! dbu.chkClassNameValidity(this.DBconnection, role_type.OWLclassName))
    	{
        	this.fatalErrorMsg +=   this.errMsgFiller 
        	                      + "Unknown " + roleType + " (OWL class) name ("+ role_type.OWLclassName + ").\n\n" ;
        	this.fatalErrorCount++ ;
    	}

    	if (role_type.dataPropertyName != null)
    	{
            if (! dbu.chkDataPropertyValidity(this.DBconnection, role_type.dataPropertyName))
            {
    		    this.fatalErrorMsg +=   this.errMsgFiller 
    		                          + "Unknown " + roleType + " data property name (" 
                                      + role_type.dataPropertyName + ") at class name " + role_type.OWLclassName + ".\n\n" ;
            	this.fatalErrorCount++ ;
            }
    	}
    	
    	if (role_type.dataPropertyValue != null)
    	{
    	    String data_property_type  = dbu.getDataPropertyType(this.DBconnection, role_type.dataPropertyName) ;
    	    
    	    ChkDataPropertyValueConsistency cpvc = new ChkDataPropertyValueConsistency() ;
    	    
    	    boolean consistencyCheckResult = cpvc.check(role_type.dataPropertyValue, data_property_type) ;

    	    if (consistencyCheckResult == false)
    	    {
        	    if (data_property_type.length() == 0)
        	    	data_property_type = "'could not be evaluated'" ;

    	    	this.fatalErrorMsg +=   this.errMsgFiller 
    	                      + "Data property value/type inconsistency: '" 
    	                      +  role_type.dataPropertyValue + "' does not conform with " 
    	                      +  data_property_type + ").\n" + this.errMsgFiller
    	                      +  "OWL class name is " + role_type.OWLclassName + ", " 
    	                      +  "data_property_name is " + role_type.dataPropertyName + ".\n\n" ; 
            	this.fatalErrorCount++ ;
  	        }
    	}

    }   //  end of method chkParticipantData()

    private void __test_ShowRelationData(TYPE_OF_RELATION r)
    {
        System.out.println("----Relation-------------------------------------------------------------" );
        System.out.println("\nn_ary_relation_id is >" + this.n_ary_relation_id + "<") ;
        System.out.println("predicate  is          >" + r.predicate            + "<") ;
        System.out.println("annotation is          >" + r.relationAnnotation   + "<") ;
        System.out.println("casualEventIndividual  >" + gni.getNewIndividualName("Collective_Causal_Event") + "<") ;

    }   //  end of method __test_ShowRelationData()
    
    private void __test_ShowParticipantData(String roleType, RelationMemberData role_type)
    {
    	System.out.println("----" + roleType + "-----------------------------------------------------" );
        System.out.println("    n_ary_relation_id is     >" + this.n_ary_relation_id + "<") ;
        System.out.println("    Subject is               >" + role_type.OWLclassName         + "<") ;
        System.out.println("    Subject individual is    >" + gni.getNewIndividualName(role_type.OWLclassName) + "<") ;
        System.out.println("    role_type is             >" + roleType               + "<\n") ;
    
        System.out.println("    participant_id is        >" + this.participant_id    + "<") ;
        System.out.println("    S-data property name is  >" + role_type.dataPropertyName     + "<") ;
        System.out.println("    S-data property value is >" + role_type.dataPropertyValue    + "<\n") ;

    }   //  end of method __test_ShowParticipantData()
    
    public static class TYPE_OF_RELATION 
    {
        String predicate ;
        String relationAnnotation ;
        String relationName ;
        
        ArrayList<RelationMemberData> subject ;
        ArrayList<RelationMemberData> object ;
        
        public TYPE_OF_RELATION() 
        {
            subject = new ArrayList<>() ;
            object  = new ArrayList<>() ;
            
        }   //  end of inner class parseRelationMemberData
        
    }   //  end of class TYPE_OF_RELATION
    
    public static class RelationMemberData 
    {
        String OWLclassName ;
        String dataPropertyName ;
        String dataPropertyValue ;
        
    }   //  end of class RelationMemberData
  
    private void dsplyFatalMsgAndExit(String msg, int exitCode)
    {
        System.err.println(msg) ;
        System.exit(exitCode);
    
    }   //  end of method dsplyFatalMsgAndExit()
    
}   //  end of class OWL_RelationEvaluator

/*
for source-cosmetics :)

__test_ShowParticipantData()
__test_ShowRelationData()
addSQLcontent()
chkParticipantData()
chkRelationData()
commitAndClose()
dsplyFatalMsgAndExit()
evalRelationDefs() 
extractPredicate()
extractRelationAnnotation()
extractRelationName()
parseRelationMemberData()    
processRelationDefs() 
writeRowInto_N_ARY_PARTICIPANTS()
writeRowInto_N_ARY_RELATIONS()
writeSQLfileHdr()
*/
