package com.ericsson.uecontrol.core.behaviour;

import com.ericsson.uecontrol.core.UeBehaviour;
import com.ericsson.uecontrol.core.util.UrlUtil;
import com.ericsson.uecontrol.core.util.Configurator.Configurable;

import org.apache.log4j.Logger;

import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This behaviour simulates visiting a website
 *
 * Created by ezivkoc on 2014-07-15.
 */
public class UeBehaviourSurfing extends UeBehaviour {
    private static final int BUFFER_SIZE = 512;
    protected static final int MAX_BUFFER_SIZE = 500000;
    private static final Logger log = Logger.getLogger(UeBehaviourSurfing.class);

    @Configurable("Web Address")
    private String rootUrl = "http://www.ericsson.com";

    private transient long estimatedDataLength = -1;



    private static class URLContainer{
        public URL url;
        public boolean parsable;

        private URLContainer(URL url, boolean parsable) {
            this.url = url;
            this.parsable = parsable;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            URLContainer that = (URLContainer) o;
            if (!url.equals(that.url)) return false;
            return true;
        }

        @Override
        public int hashCode() {
            return url.hashCode();
        }
    }

    @Override
    protected void execute() throws IOException {
        if(!rootUrl.startsWith("http://"))
            rootUrl = "http://"+ rootUrl;

        List<URLContainer> urlList = new ArrayList<URLContainer>();
        urlList.add(new URLContainer(new URL(rootUrl), true));
        byte[] data = new byte[BUFFER_SIZE];
        IOException retException = null;
        long totalRead = 0;

        for(int i=0; i<urlList.size(); i++) {
            if(!UrlUtil.isNetworkAvailable()){
                log.warn("Network unavailable");
                try { Thread.sleep(400); } catch (InterruptedException e) { }
                continue;
            }
            InputStream in = null;
            try {
                URLContainer cont = urlList.get(i);
                log.debug("Downloading: " + cont.url);

                URLConnection connection = UrlUtil.getURLConnection(cont.url);
                in = new BufferedInputStream(connection.getInputStream());

                if (estimatedDataLength < 0)
                    estimatedDataLength = in.available();
                int read = 0;

                StringBuilder content = new StringBuilder();
                while ((read = in.read(data)) != -1) {
                    if (stopExecution())
                        return;
                    totalRead += read;
                    super.setProgress((float) totalRead / estimatedDataLength);
                    if (cont.parsable)
                        content.append(new String(data, 0, read));
                    if (content.length() > MAX_BUFFER_SIZE)
                        break;
                }
                if (cont.parsable)
                    getAdditionalContent(cont.url, content.toString(), urlList);
            }catch(FileNotFoundException fnf){
                log.warn("Unable to download: "+urlList.get(i).url);
            }catch(IOException e){
                log.warn(null, e);
                if(retException == null) retException = e;
            }finally {
                if(in != null) in.close();
            }
        }

        estimatedDataLength = totalRead;
        log.debug("Total download size: "+ estimatedDataLength +" bytes");
        if(retException != null)
            throw retException;
    }


    private static final Pattern[] PARSABLE_CONTENT_INCLUSION_PATTERNS = new Pattern[]{
            // HTML
            Pattern.compile("<iframe.* src=\"(.*?)\"", Pattern.CASE_INSENSITIVE),
            // CSS
            Pattern.compile("<link.*rel=\"stylesheet\".*href=\"(.*?)\".*>", Pattern.CASE_INSENSITIVE),
            Pattern.compile("@import [\"']?(.*?)[\"']?", Pattern.CASE_INSENSITIVE),
            Pattern.compile("(?:import|background)[\\W:]*url\\([\"']?(?![\"']?data:)(.*?)[\"']??\\)", Pattern.CASE_INSENSITIVE),
    };
    private static final Pattern[] CONTENT_INCLUSION_PATTERNS = new Pattern[]{
            // HTML
            Pattern.compile("<img.* src=\"(.*?)\"", Pattern.CASE_INSENSITIVE),
            // Javascript
            Pattern.compile("<script.* src=\"(.*?)\"", Pattern.CASE_INSENSITIVE)
    };
    private void getAdditionalContent(URL baseUrl, String data, List<URLContainer> urlList){
        getAdditionalContent(baseUrl, data, urlList, PARSABLE_CONTENT_INCLUSION_PATTERNS, true);
        getAdditionalContent(baseUrl, data, urlList, PARSABLE_CONTENT_INCLUSION_PATTERNS, false);
    }
    private void getAdditionalContent(URL baseUrl, String data, List<URLContainer> urlList, Pattern[] patternList, boolean parsable){
        for(Pattern pattern : patternList){
            Matcher m = pattern.matcher(data);
            while(m.find()){
                try {
                    String strUrl = m.group(1);
                    log.debug("Parsing(Regex: "+pattern.pattern()+"): " + strUrl);
                    URLContainer cont = new URLContainer(new URL(baseUrl, strUrl), parsable);
                    if(!urlList.contains(cont))
                        urlList.add(cont);
                }catch(MalformedURLException e){
                    log.warn(null, e);
                }
            }
        }
    }


    @Override
    public String getName() {
        return "Surfing";
    }

    @Override
    public String toString() {
        return "Will visit "+ rootUrl;
    }
}
