package com.xbteller.LoadOFACDBFromXML;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import com.xbteller.LoadOFACDBFromXML.constants.SDNConstants;

public class XMLParser extends DefaultHandler {
  
	private static Logger log = LogManager.getLogger();

	private Stack<Map<String, Object>> mapStack;
	private Stack<Integer> mapIndexStack;
    private List<Object> currentList;
    private Stack<String> elementNameStack;
    
    private List<String> elementsWithMaps;
    private List<String> elementsWithLists;
    private List<String> elementsWhichHaveNoMeaningfulProximateCharactersMethodCall;

    private boolean skipCharacterCheck;
    
    private int count;

    public XMLParser() {
        count = 0;
    	
        mapStack = new Stack<>();
        elementNameStack = new Stack<>();
        
        elementsWithMaps = new ArrayList<>();
        elementsWithLists = new ArrayList<>();
        elementsWhichHaveNoMeaningfulProximateCharactersMethodCall = new ArrayList<>();
        
        populateElementsWithMapsList();
        populateElementsWithListsList();
        populateElementsWhichHaveNoMeaningfulProximateCharactersMethodCall();
    }
    

    public void startDocument() throws SAXException {
    	log.info("Start XML document processing..");
    }

    public void endDocument() throws SAXException {
    	log.info("End XML document processing..");
    }
    
    public void startElement(String uri, String localName, String qName, Attributes attrs) {
    	log.info("Start element [" + qName + "]");
//    	System.out.println("Start element: " + qName);
    	
    	skipCharacterCheck = elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.contains(qName);

        if (elementsWithMaps.contains(qName)) {
        	Map<String, Object> map = new HashMap<>();
        	
        	mapStack.push(map);
        	count++;
        	
        	System.out.println("PUSHED new MAP for [" + qName + "]");
        } else if (elementsWithLists.contains(qName)) {
        	currentList = new LinkedList<Object>();
        	System.out.println("Created new LIST for [" + qName + "]");
        }
	
        elementNameStack.push(qName);
    }

    public void characters(char ch[], int start, int length) {
    	if (!skipCharacterCheck) {
    	
	    	if (elementNameStack.size() > 0) {
		    	String chString = new String(ch, start, length);
	    		String elementName = elementNameStack.pop();
	    		
		    	if (currentList == null || ( elementNameStack.size() > 0 && elementsWithMaps.contains(elementNameStack.peek()))) {
		    		Map<String, Object> map = (Map<String, Object>)mapStack.peek();
		        	map.put(elementName, chString);
		        	
		        	System.out.println("to map [" + elementNameStack.peek() + "] added char data for [" + elementName + "] = " + chString);
		    	}
		    	else {
		    		currentList.add(chString);
		    		
		    		System.out.println("to List for [" + elementNameStack.peek() + "] added " + chString);
		    	}
		    	
		    	elementNameStack.push(elementName);
	    	}

    		skipCharacterCheck = true; // finished this element, don't respond to any more char calls till next element.
    	}
    }
    
    public void endElement(String uri, String localName, String qName) {
        if (elementNameStack.size() > 0) {
	        String elementName = elementNameStack.pop();
	        
	        log.info("End element [" + elementName + "]");
//	        System.out.println("End element: " + qName);
	        
	        if (elementsWithMaps.contains(elementName)) {
	        	
	        	if (currentList == null) {
	        		if (mapStack.size() > 1) {
		        		Map<String, Object> map = mapStack.pop();
		        		Map<String, Object> parentMap = (Map<String, Object>)mapStack.peek();
			        	parentMap.put(elementName+count, map);
			        	
			        	System.out.println("POPPED map [" + elementName + "] and added it to map for [" + elementNameStack.peek() + ", " + parentMap.size() + " elements.]");
	        		}
	        	}
	        	else {
	        		Map<String, Object> map = mapStack.pop();
	        		currentList.add(map);
	        		
	        		System.out.println("POPPED map [" + qName + "] and added it to list for [" + elementNameStack.peek() + ", " + map.size() + " elements.]");
	        	}
	        }
	        
	        if (elementsWithLists.contains(elementName)) {
	        	Map<String, Object> map = (Map<String, Object>)mapStack.peek();
	        	
	        	map.put(elementName+count, currentList);
	        	
	        	System.out.println("to map [" + elementNameStack.peek() + "] added current list for [" + qName + "]");
	        	
	        	currentList = null;
	        }
        }
    }
    
    public Map<String, Object> getResults() {
    	return mapStack.peek();
    }
    
    private void populateElementsWhichHaveNoMeaningfulProximateCharactersMethodCall() {
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.clear();
    	
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_SDNLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_SDNENTRY);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_PGMLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_AKALIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_AKA);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_ADDRESSLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_ADDRESS);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_IDLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_ID);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_NATIONALITYLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_NATIONALITY);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_CITIZENSHIPLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_CITIZENSHIP);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_PLACEOFBIRTHLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_PLACEOFBIRTH);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_DATEOFBIRTHLIST);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_DATEOFBIRTH);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_VESSELINFO);
    	elementsWhichHaveNoMeaningfulProximateCharactersMethodCall.add(SDNConstants.ELEMENT_PUBLISHINFORMATION);
    }
    
    private void populateElementsWithMapsList() {
    	elementsWithMaps.clear();
    	
    	elementsWithMaps.add(SDNConstants.ELEMENT_SDNLIST);
    	elementsWithMaps.add(SDNConstants.ELEMENT_SDNENTRY);
    	elementsWithMaps.add(SDNConstants.ELEMENT_AKA);
    	elementsWithMaps.add(SDNConstants.ELEMENT_ADDRESS);
    	elementsWithMaps.add(SDNConstants.ELEMENT_ID);
    	elementsWithMaps.add(SDNConstants.ELEMENT_NATIONALITY);
    	elementsWithMaps.add(SDNConstants.ELEMENT_CITIZENSHIP);
    	elementsWithMaps.add(SDNConstants.ELEMENT_PLACEOFBIRTH);
    	elementsWithMaps.add(SDNConstants.ELEMENT_DATEOFBIRTH);
    	elementsWithMaps.add(SDNConstants.ELEMENT_VESSELINFO);
    	elementsWithMaps.add(SDNConstants.ELEMENT_PUBLISHINFORMATION);
    }
    
    private void populateElementsWithListsList() {
    	elementsWithLists.clear();
    	
    	elementsWithLists.add(SDNConstants.ELEMENT_PGMLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_AKALIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_ADDRESSLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_IDLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_NATIONALITYLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_CITIZENSHIPLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_PLACEOFBIRTHLIST);
    	elementsWithLists.add(SDNConstants.ELEMENT_DATEOFBIRTHLIST);
    }
}
