/*
 * Copyright (C) 2009-2017 Alistair Neil <info@dazzleships.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package lib;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JProgressBar;

/**
 *
 * @author Alistair Neil, <info@dazzleships.net>
 */
public class NetFunctions {

    public static final int CONNECTION_FAILED = 0;
    public static final int FILE_LOCAL_ISNEWER = 1;
    public static final int FILE_RETRIEVED = 2;
    public static final int FILE_FAILED = 3;
    public static final int EVENT_URLOK = 1;
    public static final int EVENT_URLFAILED = 2;
    public static final int EVENT_NETLATENCY = 3;
    public static final long LATENCY_FAIL = 9999;
    public static final long LATENCY_UNKNOWN = 0;
    private int intSocketTimeout = 8000;
    private JProgressBar jpb;

    public NetFunctions() {
    }

    /**
     * Set default socket timeout
     *
     * @param timeout
     */
    public void setSocketTimeout(int timeout) {
        intSocketTimeout = timeout;
    }

    public void setProgressBar(JProgressBar jpb) {
        this.jpb = jpb;
    }

    /**
     * Save the contents of a URL to a file
     *
     * @param destfile File destination path
     * @param sourceurl URL
     * @param socket
     * @param force Forces update regardless of age
     * @return int File retieval status code
     */
    public int saveURLContentToFile(String destfile, String sourceurl, Socket socket, boolean force) {
        // Get a handle to our local file
        File f = new File(destfile);
        InputStream is = openStreamToURL(sourceurl, socket);
        HttpPage hp = new HttpPage(is);
        int result = FILE_FAILED;
        try {
            socket.setSoTimeout(intSocketTimeout);
            hp.retrieveHeader();
            if (hp.isHeaderOK()) {
                result = FILE_LOCAL_ISNEWER;
                if (hp.getLastModified() > f.lastModified() || force) {
                    if (hp.saveBodyToFile(destfile, jpb)) {
                        result = FILE_RETRIEVED;
                        Logger.getGlobal().logp(Level.INFO, this.getClass().getName(), "saveURLContentToFile", "Http body content retrieved");
                    } else {
                        Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "saveURLContentToFile", "Failed to retrieve Http body content");
                    }
                }
            } else {
                Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "saveURLContentToFile", "Failed to retrieve Http header");
            }
        } catch (IOException ex) {
            Logger.getGlobal().logp(Level.WARNING, this.getClass().getName(), "saveURLContentToFile", ex.getMessage());
        } finally {
            try {
                is.close();
                socket.close();
            } catch (Exception ex) {
            }
        }
        return result;
    }

    /**
     * Make sure ipaddress is valid number
     *
     * @param ipaddress
     * @return true if a valid ip address
     */
    public boolean isIPAddress(String ipaddress) {
        ipaddress = ipaddress.replace(".", "");
        try {
            Long.parseLong(ipaddress);
            return true;
        } catch (NumberFormatException ex) {
        }
        return false;
    }

    /**
     * Get latency non threaded version
     *
     * @param surl Testing url
     * @param proxy Specify a proxy, null == no proxy
     * @param timeout Specify a timeout before declaring failure
     * @return long latency in milliseconds
     */
    public long getLatency(String surl, Proxy proxy, int timeout) {

        long lngStart;
        long lngResult;
        HttpURLConnection conn = null;

        try {
            URL url = new URL(surl);
            if (proxy == null) {
                conn = (HttpURLConnection) url.openConnection();
            } else {
                conn = (HttpURLConnection) url.openConnection(proxy);
            }
            conn.setDoInput(true);
            conn.setDefaultUseCaches(false);
            conn.setConnectTimeout(timeout);
            lngStart = System.currentTimeMillis();
            conn.connect();
            lngResult = System.currentTimeMillis() - lngStart;
        } catch (IOException ex) {
            lngResult = LATENCY_FAIL;
            Logger.getGlobal().log(Level.WARNING, "getLatency {0}", ex.getMessage());
        }
        if (conn != null) {
            conn.disconnect();
        }
        return lngResult;
    }

    /**
     * Check web for updates and return version string if an update is available
     *
     * @param sourceurl URL to check
     * @param s Socket
     * @return String new version number
     */
    public String getURLContentAsString(String sourceurl, Socket s) {

        String result = null;
        try {
            s.setSoTimeout(intSocketTimeout);
            try (InputStream is = openStreamToURL(sourceurl, s)) {
                HttpPage hp = new HttpPage(is);
                hp.retrieveHeader();
                if (hp.isHeaderOK()) {
                    result = hp.getBodyAsString();
                }
                is.close();
            }
        } catch (Exception ex) {
            Logger.getGlobal().log(Level.WARNING, "getURLAsString", ex.getMessage());
            result = null;
        } finally {
            try {
                s.close();
            } catch (Exception ex) {
            }
        }
        return result;
    }

    /**
     * Open an inputstream to the given url on the given socket
     *
     * @param surl
     * @param s
     * @return inputstream
     */
    public InputStream openStreamToURL(String surl, Socket s) {
        URI uri;
        try {
            uri = new URI(surl);
            String host = uri.getHost();
            String path = uri.getRawPath();
            if (path == null) {
                return null;
            }

            PrintWriter request = new PrintWriter(s.getOutputStream());
            request.print("GET " + path + " HTTP/1.1\r\n"
                    + "Host: " + host + "\r\n"
                    + "Connection: close\r\n\r\n"
            );

            request.flush();
            InputStream inStream = s.getInputStream();
            return inStream;
        } catch (URISyntaxException | IOException ex) {
        }
        return null;
    }

    /**
     * Store an http formatted web page
     */
    public static final class HttpPage {

        public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
        public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";
        public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
        private final TimeZone GMT = TimeZone.getTimeZone("GMT");
        private final SimpleDateFormat format = new SimpleDateFormat("", Locale.US);
        private final HashMap<String, String> hm = new HashMap<>();
        private final InputStream israw;

        /**
         * Store webpage from the given input stream
         *
         * @param is
         */
        public HttpPage(InputStream is) {
            format.setTimeZone(GMT);
            israw = is;
        }

        private String readLine(InputStream is) {
            byte data;
            String line = "";
            String term = "";
            try {
                while (!"\r\n".equals(term)) {
                    data = (byte) is.read();
                    if (data == -1) {
                        return null;
                    }
                    line += String.valueOf((char) data);
                    if (((char) data) == '\r' || ((char) data) == '\n') {
                        term += String.valueOf((char) data);
                    } else {
                        term = "";
                    }
                }
                line = line.trim();
            } catch (Exception ex) {
                line = null;
            }
            return line;
        }

        /**
         * Retrieve http header info
         */
        public final void retrieveHeader() {
            int colonpos;
            String key;
            String value;
            String line;
            try {
                line = readLine(israw);
                hm.put("Header-Response", line);
                while (true) {
                    line = readLine(israw);
                    if (line.isEmpty()) {
                        if (hm.get("Content-Length") == null) {
                            line = readLine(israw);
                            if (!line.isEmpty()) {
                                try {
                                    long contentsize = Long.parseLong(line, 16);
                                    hm.put("Content-Length", String.valueOf(contentsize));
                                } catch (Exception ex) {
                                }
                            }
                        }
                        break;
                    }
                    colonpos = line.indexOf(':');
                    key = line.substring(0, colonpos);
                    value = line.substring(colonpos + 1);
                    hm.put(key, value.trim());
                }
            } catch (Exception ex) {
                Logger.getGlobal().log(Level.WARNING, "retrieveHeader()", ex.getMessage());
                hm.clear();
            }
        }

        /**
         * Retrieve http body as string should only be used with text content
         *
         * @return null if failed, otherwise returns the contents
         */
        public final String getBodyAsString() {
            String result = "";
            try {
                String temp = hm.get("Content-Length");
                if (temp == null) {
                    return null;
                }
                long contentsize = Long.parseLong(temp);
                while (contentsize-- != 0) {
                    result += (char) israw.read();
                }
            } catch (NumberFormatException | IOException ex) {
                result = null;
                Logger.getGlobal().log(Level.WARNING, "getBodyAsString()", ex.getMessage());
            }
            return result;
        }

        /**
         * Save http body to a file, applies to all types of content
         *
         * @param dest File destination
         * @param jpb Progress bar
         * @return true if successful
         */
        public final boolean saveBodyToFile(String dest, JProgressBar jpb) {
            try {
                String temp = hm.get("Content-Length");
                if (temp == null) {
                    return false;
                }
                long contentsize = Long.parseLong(temp);
                long bytereset = contentsize / 102;
                long bytecount = bytereset;
                int progress = 0;
                File f = new File(dest);
                try (FileOutputStream fos = new FileOutputStream(f)) {
                    if (jpb != null) {
                        jpb.setString(null);
                    }
                    while (contentsize-- != 0) {
                        fos.write(israw.read());
                        if (bytecount-- == 0) {
                            bytecount = bytereset;
                            if (jpb != null) {
                                jpb.setValue(progress++);
                            }
                        }
                    }
                    fos.close();
                    return true;
                }
            } catch (NumberFormatException | IOException ex) {
                Logger.getGlobal().log(Level.WARNING, "saveBody()", ex.getMessage());
            }
            return false;
        }

        /**
         * Get various properties of the web page
         *
         * @param key
         * @return property string
         */
        public final String getProperty(String key) {
            return hm.get(key);
        }

        private long getHttpDate(String adate) {
            try {
                format.applyPattern(PATTERN_RFC1123);
                return format.parse(adate).getTime();
            } catch (Exception ex) {
            }
            try {
                format.applyPattern(PATTERN_RFC1036);
                return format.parse(adate).getTime();
            } catch (Exception ex) {
            }
            try {
                format.applyPattern(PATTERN_ASCTIME);
                return format.parse(adate).getTime();
            } catch (Exception ex) {
            }
            return -1;
        }

        public final long getLastModified() {
            return getHttpDate(hm.get("Last-Modified"));
        }

        public final long getServerDate() {
            return getHttpDate(hm.get("Date"));
        }

        public final long getExpires() {
            return getHttpDate(hm.get("Expires"));
        }

        public final boolean isHeaderOK() {
            String temp = hm.get("Content-Length");
            if (temp == null) {
                return false;
            }
            temp = hm.get("Header-Response");
            if (temp == null) {
                return false;
            }
            return temp.contains("200 OK");
        }

    }

}
