package com.eactive.eai.transformer.layout;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.eactive.eai.common.lifecycle.Lifecycle;
import com.eactive.eai.common.lifecycle.LifecycleException;
import com.eactive.eai.common.lifecycle.LifecycleListener;
import com.eactive.eai.common.lifecycle.LifecycleSupport;
import com.eactive.eai.common.util.ApplicationContextProvider;
import com.eactive.eai.transformer.dao.LayoutDao;
import com.eactive.eai.transformer.dao.jdbc.LayoutJdbcDao;
import com.eactive.eai.transformer.message.MessageException;
import com.eactive.eai.transformer.message.MessageFactory;
import com.eactive.eai.transformer.util.Logger;
import com.eactive.eai.transformer.util.MemoryLogger;

@Component
public class LayoutManager implements Lifecycle {
    private static Logger logger = Logger.getLogger("transformer");

    private Map<String, Layout> layoutMap;
    private Map<String, String> dependencyMap; 
    private Map<String, String> permanentMap;
    private LifecycleSupport lifecycleSupport = null;
    private boolean started;
    
    @Autowired
    private LayoutDao dao;
    
    @Autowired
    private LayoutJdbcDao layoutJdbcDao;

    private LayoutManager() {
        layoutMap = new ConcurrentHashMap<>();
        dependencyMap = new HashMap<>();
        lifecycleSupport = new LifecycleSupport(this);
    }

    public static LayoutManager getManager() {
        return ApplicationContextProvider.getContext().getBean(LayoutManager.class);
    }

    public synchronized void reload() throws Exception {
        /**
         * memory logging
         */
    	logger.info("LayoutManager | start reloading all layouts");
    	MemoryLogger.txlog("LayoutManager | start reloading all layouts");

        init(false);
        
    	logger.info("LayoutManager | end reloading all layouts");
    	MemoryLogger.txlog("LayoutManager | end reloading all layouts");
    }
    
    public synchronized void permanentReload() {
    	final Map<String, String> tempPermanetMap = dao.findPermanent();
    	permanentMap = tempPermanetMap;
    	for(String key : permanentMap.keySet()){
        	if (!MessageFactory.getFactory().checkPerment(key)){
        		MessageFactory.getFactory().init(key);
        	}
        }
    	
    }
    //agent 에서 reload
    public synchronized void permanentReload(String layoutName) {
    	final Map<String, String> tempPermanetMap = dao.findPermanent();
    	if (tempPermanetMap.containsKey(layoutName)){
   			MessageFactory.getFactory().init(layoutName);
    	}
    	
    }

    public synchronized void reload(String layoutName) throws MessageException {
    	reload(layoutName, 0);
    }

    private void reload(String layoutName, int level) throws MessageException {
    	if (level > 10) {
    		throw new MessageException("cannot reload. check if message dependency (include) has cycle");
    	}
    	
    	try {
	        Layout layout = dao.findLayoutByName(layoutName);
	        
	        if (layout == null || "".equals(layout.getName()))
	        	throw new MessageException("layout(" + layoutName + ") not found");

	        if (!"1".equals(layout.getUseYn())){
	        	layoutMap.remove(layoutName);
	        	MessageFactory.getFactory().removeCache(layoutName);
	        	logger.warn("layout reload["+layoutName+"] useYn["+layout.getUseYn()+"] removed");
	        	return;
	        }
	        Item root = dao.findItemByName(layoutName);
	        
	        if (root == null)
	        	throw new MessageException("layout(" + layoutName + ")'s root item not found");

	        layout.setRootItem(root);
	        
	        layout.init();
	
	        putLayout(layout);
	        
	        //-----------------------------------------------------------------------
	    	// 2009.11.18 Cache 문제로 인한 테스트 
	    	boolean cacheDeleted = MessageFactory.getFactory().removeCache(layoutName);
	    	if(logger.isWarnEnabled()) {
	    		logger.warn("LayoutManager] reload - MessageFactory Remove Cache ["+layoutName+"] - "+cacheDeleted);
	    	}
	    	//-----------------------------------------------------------------------
	        if (!layoutName.equals(layout.getName())) {
	        	throw new MessageException("layout name is different. '" + 
	        			layoutName + "' != '" + layout.getName() + "'");
	        }
	        
	        logger.info("LayoutManager | reloaded - " + layoutName + (level>0 ? " - dependency(+" + level + ")"  : ""));
	    	MemoryLogger.txlog("LayoutManager | layout reloaded - " + layoutName + (level>0 ? " - dependency(+" + level + ")"  : "") + 
	    			"\n" + layout.getRootItem());
	        
	        /**
	         * check dependency -> reload dependent layout recursively
	         */
	    	for (Iterator<String> iter = dependencyMap.keySet().iterator(); iter.hasNext(); ) {
	    		String parent = iter.next();
	    		String child = dependencyMap.get(parent);
	    		if (child != null && child.equals(layoutName)) {
	    			reload(parent, ++level);
	    		}
	    	}
    	} catch(Exception e) {
    		layoutMap.remove(layoutName);
			logger.error("LayoutManager | reload faild - '" + layoutName + "'", e);		
	    	MemoryLogger.error("LayoutManager | reload faild - '" + layoutName + "'", e);
	    	throw new MessageException("layout reload faild - " + layoutName, e);
    	}
    }

    public synchronized void reload(String layoutName, String jname, String schema, LayoutType layoutType) throws MessageException {
   		reload(layoutName, 0 , jname, schema, layoutType);
    }
    public synchronized void reload(String layoutName, String jname, String schema) throws MessageException {
    	reload(layoutName, 0 , jname, schema, null);
    }
      
      private void reload(String layoutName, int level ,String jname, String schema, LayoutType layoutType) throws MessageException {
      	if (level > 10) {
      		throw new MessageException("cannot reload. check if message dependency (include) has cycle");
      	}
      	
      	try {
  	        Layout layout = dao.findLayoutByName(layoutName);

  	        if (layout == null || "".equals(layout.getName()))
  	        	throw new MessageException("layout(" + layoutName + ") not found");

  	        if(layoutType!=null){
		        layout.setLayoutType(layoutType);
  	        }  	        

  	        Item root = dao.findItemByName(layoutName);
  	        
  	        if (root == null)
  	        	throw new MessageException("layout(" + layoutName + ")'s root item not found");

  	        layout.setRootItem(root);
  	        
  	        layout.init();
  	
  	        putLayout(layout);
  	        
  	        //-----------------------------------------------------------------------
  	    	// 2009.11.18 Cache 문제로 인한 테스트 
  	    	boolean cacheDeleted = MessageFactory.getFactory().removeCache(layoutName);
  	    	if(logger.isWarnEnabled()) {
  	    		logger.warn("LayoutManager] reload - MessageFactory Remove Cache ["+layoutName+"] - "+cacheDeleted);
  	    	}
  	    	//-----------------------------------------------------------------------
  	        if (!layoutName.equals(layout.getName())) {
  	        	throw new MessageException("layout name is different. '" + 
  	        			layoutName + "' != '" + layout.getName() + "'");
  	        }
  	        
  	        logger.info("LayoutManager | reloaded - " + layoutName + (level>0 ? " - dependency(+" + level + ")"  : ""));
  	    	MemoryLogger.txlog("LayoutManager | layout reloaded - " + layoutName + (level>0 ? " - dependency(+" + level + ")"  : "") + 
  	    			"\n" + layout.getRootItem());
  	        
  	        /**
  	         * check dependency -> reload dependent layout recursively
  	         */
  	    	for (Iterator<String> iter = dependencyMap.keySet().iterator(); iter.hasNext(); ) {
  	    		String parent = iter.next();
  	    		String child = dependencyMap.get(parent);
  	    		if(child == null) continue;
  	    		if (child.equals(layoutName)) {
  	    			reload(parent, ++level, jname, schema, layoutType);
  	    		}
  	    	}
      	} catch(Exception e) {
      		layoutMap.remove(layoutName);
  			logger.error("LayoutManager | reload faild - '" + layoutName + "'", e);		
  	    	MemoryLogger.error("LayoutManager | reload faild - '" + layoutName + "'", e);
  	    	throw new MessageException("layout reload faild - " + layoutName, e);
      	}
      }
    public synchronized void remove(String layoutName) throws MessageException {
    	layoutMap.remove(layoutName);
    	
    	//-----------------------------------------------------------------------
    	// 2009.11.18 Cache 문제로 인한 테스트 
    	boolean cacheDeleted = MessageFactory.getFactory().removeCache(layoutName);
    	if(logger.isWarnEnabled()) {
    		logger.warn("LayoutManager] remove - MessageFactory Remove Cache ["+layoutName+"] - "+cacheDeleted);
    	}
    	//-----------------------------------------------------------------------
    }

    public void addLifecycleListener(LifecycleListener listener) {
        lifecycleSupport.addLifecycleListener(listener);
    }

    public LifecycleListener[] findLifecycleListeners() {
        return lifecycleSupport.findLifecycleListeners();
    }

    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycleSupport.removeLifecycleListener(listener);
    }

    public synchronized void start() throws LifecycleException {
        try {
            init(true);
            // Tuning 
            // 기동시에 Cache를 초기화하도록 함.
            MessageFactory.getFactory().initCache();
            this.started = true;
        } catch (Exception e) {
            throw new LifecycleException(e);
        }
    }

    public synchronized void stop() throws LifecycleException {
        try {
            clear();
            this.started = false;
        } catch (Exception e) {
            throw new LifecycleException(e);
        }
    }

    public boolean isStarted() {
        return this.started;
    }
    
    
    public String[] getAllLayoutName() {
        Iterator<String> it = layoutMap.keySet().iterator();
        
        String[] keys = new String [layoutMap.size()];
        
        for (int i=0; i<keys.length; i++) {
            keys[i] = it.next();   
        }
        Arrays.sort(keys);
        return keys;
    }
    
    public Map<String, Layout> getLayouts() {
    	return layoutMap;    	
    }
    
    public Layout getLayoutByName(String layoutName) {
    	if(layoutName == null) {
    		String errorMsg = "LayoutManager | layoutName is NULL - "+layoutName;
    		logger.error(errorMsg);
    		MemoryLogger.error(errorMsg);
    		return null;
    	}

    	Object obj = layoutMap.get(layoutName);
    	
    	if(obj == null) {
    		return null;
    	}
        return (Layout) obj;
    }
    
    public void putLayout(Layout layout) {
        layoutMap.put(layout.getName(), layout);
    }
    
    public void addDependency(String parent, String child) {
    	dependencyMap.put(parent, child);
    }
    
    private void init(boolean isFistTime) throws InterruptedException {
    	logger.info("LayoutManager | start init()");
    	
        /**
         * [1] load layouts
         */
        final Map<String, Layout> tempLayoutMap = dao.load();
        if (isFistTime) {
        	layoutMap = tempLayoutMap;
        }
        logger.info("LayoutManager | [1] load layouts completed - "+tempLayoutMap.size());
    	
        /**
         *  [2] if excel, load layout2
         */
        if (layoutJdbcDao != null) {
			Map<String, Layout> layout2Map = layoutJdbcDao.load();
        	layoutMap.putAll(layout2Map);
        	logger.info("LayoutManager | [2] if excel, load layout2 completed");
        }        
        
        /**
         * [3] init layouts
         */
        ExecutorService executor = new ThreadPoolExecutor(10, 10, 0, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
        final CountDownLatch latch  = new CountDownLatch(tempLayoutMap.size());

        for (final Iterator<String> it = tempLayoutMap.keySet().iterator(); it.hasNext();) {
            final String layoutName = it.next();

            executor.execute(new Runnable() {
				public void run() {
					Layout layout = null;
		        	try {
			            layout = tempLayoutMap.get(layoutName);
			            layout.init();
			            LayoutValidator.validate(layout);    
			            putLayout(layout);
		        	} catch(Exception e) {
		    			logger.error("LayoutManager | initialization failed - " + layoutName, e);
		        		MemoryLogger.error("LayoutManager | initialization failed - " + layoutName, e);
		        		layoutMap.remove(layout.getName());
		        	} finally {
		        		latch.countDown();
		        	}
				}
			});
        }
        
        latch.await();
        executor.shutdown();
        logger.info("LayoutManager | [3] init layouts completed");
    	
        /**
         * [4] validate layouts
         */
    	
		logger.info("LayoutManager | all layouts initialized - layoutMap size[" 
        		+ this.layoutMap.size()+"]");
        MemoryLogger.txlog("LayoutManager | all layouts initialized - layoutMap size[" 
        		+ this.layoutMap.size()+"]");
    }

    private void clear() {
        if(layoutMap != null) layoutMap.clear();
        if(dependencyMap != null) dependencyMap.clear();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[LayoutManager] toString()");

        Object[] keys = layoutMap.keySet().toArray();

        for (int i = 0; i < layoutMap.size(); i++) {
            sb.append("\n    " + keys[i] + " : " + layoutMap.get(keys[i]));
        }

        return sb.toString();
    }    
}
