package org.aksw.simba.quetsal.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fluidops.fedx.Config;
import com.fluidops.fedx.EndpointListProvider;
import com.fluidops.fedx.FedX;
import com.fluidops.fedx.structures.Endpoint;
import com.fluidops.fedx.util.EndpointFactory;

public class EndpointListFromDirectoryProvider implements EndpointListProvider {
    static Logger log = LoggerFactory.getLogger(EndpointListFromDirectoryProvider.class);
    
    List<Endpoint> endpoints = new ArrayList<Endpoint>();
    List<String> strEndpoints = new ArrayList<String>();
    Map<String, Endpoint> cache = new HashMap<String, Endpoint>();
    
    String path;
    WatchService watcher;
    WatchKey watchKey = null;
    Thread watcherThread;
    
    public EndpointListFromDirectoryProvider()
    {
        try {
            watcher = FileSystems.getDefault().newWatchService();
            watcherThread = new Thread(new WatcherProc());
            watcherThread.start();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    
    class WatcherProc implements Runnable {
        @Override
        public void run() {
            for (;;) {
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException x) {
                    return;
                }
                
                for (WatchEvent<?> event: key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();
                    if (kind == java.nio.file.StandardWatchEventKinds.OVERFLOW) {
                        continue;
                    }
                    
                    if (kind == java.nio.file.StandardWatchEventKinds.ENTRY_CREATE || kind == java.nio.file.StandardWatchEventKinds.ENTRY_DELETE || kind == java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY) {
                        rebuildEndpoints();
                    }
                }
                
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
        }
    }
    
    @Override
    public synchronized List<Endpoint> getEndpoints(FedX federation) {
        Config config = federation.getConfig();
        String path = config.getProperty("quetzal.endpoints");
        if (path == null) {
            this.path = null;
            resetEndpoints();
            return doGetEndpoints(federation);
        }
        if (!path.equals(this.path)) {
            this.path = path;
            registerWatcher();
            resetEndpoints();
            rebuildEndpoints();
        }
        return doGetEndpoints(federation);
    }

    List<Endpoint> doGetEndpoints(FedX federation) {
        if (!endpoints.isEmpty()) return endpoints;

        for (String url : strEndpoints) {
            Endpoint e = cache.get(url);
            if (e == null) {
                e = EndpointFactory.loadSPARQLEndpoint(federation.getConfig(), federation.getHttpClient(), url);
                cache.put(url, e);
            }
            endpoints.add(e);
        }
        return endpoints;
    }
    
    @Override
    public void close() {
        try {
            for (Map.Entry<String, Endpoint> entry : cache.entrySet())
            {
                entry.getValue().shutDown();
            }
            cache.clear();
            if (watcherThread != null) {
                watcherThread.interrupt();
                watcherThread.join();
                watcherThread = null;
            }
        } catch (Exception ex) {
            log.error("", ex);
        }
    }

    void resetEndpoints() {
        endpoints.clear();;
        strEndpoints.clear();
    }

    synchronized void registerWatcher() {
        try {
            if (watchKey != null) {
                watchKey.cancel();
                watchKey = null;
            }
            
            Path dir = Paths.get(path);
            watchKey = dir.register(watcher, java.nio.file.StandardWatchEventKinds.ENTRY_CREATE, java.nio.file.StandardWatchEventKinds.ENTRY_DELETE, java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY);

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    
    synchronized void rebuildEndpoints() {
        endpoints.clear();
        strEndpoints.clear();
        
        File folder = new File(path);
        
        for (final File fileEntry : folder.listFiles()) {
            if (fileEntry.isDirectory()) continue;
            if (fileEntry.getName().endsWith(".temp")) continue;
            
            Properties prop = loadEndpoint(fileEntry);
            if (prop == null) continue;
            
            boolean enable = "true".equals(prop.getProperty("enable"));
            if (!enable) continue;
            
            boolean hasValidSummary = "ready".equals(prop.getProperty("summary"));
            if (!hasValidSummary) continue;
            
            String address = prop.getProperty("address");
            if (address != null) {
                strEndpoints.add(address);
            }
        }
        log.info("endpoints have been rebuilt: " + strEndpoints);
    }
    
    private Properties loadEndpoint(File file)
    {
        Properties prop = new Properties();
        try {
            InputStream is = new FileInputStream(file);
            try {
                prop.load(is);
            } finally {
                is.close();
            }
        } catch (IOException e) {
            return null;
        }
        return prop;
    }
}