package josmlaunch;

import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;

import java.net.URL;
import java.net.URLConnection;
import java.util.Properties;
import java.util.jar.JarInputStream;
import java.util.zip.ZipEntry;
import javax.swing.*;


class JosmLauncher extends JFrame implements PropertyChangeListener {
    
    JButton start = new JButton("---");
    JButton update = new JButton("---");
    JTextField opts = new JTextField(50);
    JCheckBox portable = new JCheckBox("Portable installation");
    JProgressBar progress = new JProgressBar(0, 100);
    JComboBox cbInterval = new JComboBox(new String[]{"7","14","30","3","1"});
    JComboBox cbTested = new JComboBox(
            new String[]{"josm-tested","josm-latest"});
    
    Properties properties = new Properties();
    
    int availableVersion;
    int currentVersion;
    boolean versionChecked = false;
    
    String getJosmAddress() {
        return String.format("http://josm.openstreetmap.de/download/josm-%s.jar",
                cbTested.getSelectedIndex()==0? "tested" : "latest");
    }

    String getVersionCheckAddress() {
        return String.format("http://josm.openstreetmap.de/%s",
                cbTested.getSelectedIndex()==0? "tested" : "latest");
    }

    String workDir="";
            
    File jarToRun;
    File fileWithNewVersion;
    File backupFile;
    File iniFile;
    
    DownloadTask dt = null;

    private String updateText;
    private String startText;
    
    private boolean launchOnDownload;

    private long lastUpdate;
    private long updateInterval;

    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        if ("progress".equals(evt.getPropertyName())) {
            int val = ( (Integer) evt.getNewValue() ).intValue();
            progress.setValue(val);
            if (dt!=null) {
            progress.setString(
                    String.format("Downloading josm-latest.jar v%d: %d of %d Kb", availableVersion, dt.downloadedBytes/1024,  dt.downloadSize/1024));
            }
            progress.setStringPainted(true);
        }
    }
   
    public JosmLauncher() {
        
        setupDirectories();
        loadProperties();
        
        setSize(350,200);
        center();
        
        start.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                launch();
            }
        });
        
        update.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                updateJar();
            }
        });
        
        GridLayout gb =new GridLayout(5,2);
        setLayout(gb);
        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                saveProperties();
                System.exit(0); 
            }
        } );
        
        startText = "Start JOSM";
        
        JPanel p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(portable);
        p.add(cbTested);
        add(p);  
        
        cbTested.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (cbTested.getSelectedIndex()==1) {
                     JOptionPane.showMessageDialog(JosmLauncher.this, 
                             "Josm-latest version (released daily) may have more errors than \"tested\", so be careful!", "Warning", JOptionPane.WARNING_MESSAGE);
                }
                setIntervalList();
                checkFreshVersion(true);
            }

        });
        
        p = new JPanel();
        p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
        p.add(new JLabel("Java VM arguments: "));
        p.add(opts);
        add(p);  
       
        p = new JPanel();
        p.add(new JLabel("Automatic update interval(days): "));
        p.add(cbInterval);
        add(p);  
       // add(opts);
        add(start);
        add(update);
        
    }
    
    
    void updateJar() {
        if (dt!=null) { // cancel download
            dt.cancel(true);
            setUIState(CHOOSE );
            dt = null;
            return;
        }
        
        if (checkFreshVersion(false)) {
            if (launchOnDownload) launch();
            return;
        }
        
        setVisible(true);
        dt = new DownloadTask(getJosmAddress());
        dt.addPropertyChangeListener(this);
        dt.execute();
        setUIState(DOWNLOADING);
    }

    void setIntervalList() {
        String[] its;
        if (cbTested.getSelectedIndex()==1) {
            its = new String[]{"10", "7", "5", "3", "2", "1"};
        } else {
            its = new String[]{"30", "40", "50", "60"};
        }
        cbInterval.removeAllItems();
        for (String s: its) cbInterval.addItem(s);
    }
    
    private boolean checkFreshVersion(boolean checkNow) {
        setupDirectories();
        if (!versionChecked || checkNow) {
            GetVersionTask vt = new GetVersionTask(getVersionCheckAddress());
            vt.execute();
            try {
                availableVersion = vt.get();
                System.err.printf("Version %d found on server\n",availableVersion);
            } catch (Exception ex) {
                JOptionPane.showMessageDialog(this, "Sorry, can not get JOSM version. There is some connection problem", "Warning", JOptionPane.WARNING_MESSAGE);
                availableVersion = 0;
            }
            updateText = "Update to version "+availableVersion;
            
            readVersionFromJar();
            versionChecked = true;
        }
        
        
        if (currentVersion >= availableVersion) {
            //if (showMessage) {
            //    JOptionPane.showMessageDialog(this, "Your JOSM version "+currentVersion
            //        +" does not need updating", "", JOptionPane.INFORMATION_MESSAGE);
            //}
            setUIState(STARTONLY);
            return true;
        }
        
        if (currentVersion > 0) {
            setUIState(CHOOSE);
        } else {
            setUIState(DOWNLOADONLY);
        }
        return false;
    }


    
    String getProperty(String key, String def) {
        return properties.getProperty(key, def);
    } 
    
    void loadProperties() {
        FileReader fr = null;
        try {
            fr = new FileReader(iniFile);
            properties.load(fr);
        } catch (FileNotFoundException ex) {
            System.err.println("Properties file is not found, will be created");
        } catch (IOException ex) {
            System.err.println("Can not read properties file");
        } finally {
            try {
                if (fr!=null) fr.close();
            } catch (IOException ex) {   }
        }
        cbTested.setSelectedIndex(getProperty("josm-tested", "true").equals("true")?0:1);
        opts.setText(getProperty("options", "-Xmx1024m"));
        portable.setSelected(getProperty("portable", "false").equals("true"));
        
        
        lastUpdate = Long.parseLong(getProperty("lastUpdate", "0"));
        String ui = getProperty("updateInterval", "7");
        updateInterval = Long.parseLong(ui);
        
        setIntervalList();
        cbInterval.setSelectedItem(String.valueOf(updateInterval));
        if (!ui.equals( cbInterval.getSelectedItem()) ) {
            cbInterval.addItem(ui);
            cbInterval.setSelectedItem(ui);
        }
        
        
    }
    
    void saveProperties() {
        properties.setProperty("options", opts.getText());
        properties.setProperty("portable", String.valueOf(portable.isSelected()));
        properties.setProperty("lastUpdate", String.valueOf(lastUpdate));
        properties.setProperty("updateInterval", cbInterval.getSelectedItem().toString());
        properties.setProperty("josm-tested", cbTested.getSelectedIndex()==0?"true":"false");
        
        FileWriter fr = null;
        try {
            fr = new FileWriter(iniFile);
            properties.store(fr,"");
        } catch (IOException ex) {
            System.err.println("Can not write properties file");
        } finally {
            try {
                if (fr!=null) fr.close();
            } catch (IOException ex) {   }
        }
    }
    
    void readVersionFromJar() {
        currentVersion = 0;
        FileInputStream fis = null;
        JarInputStream jis = null;
        
        if (jarToRun == null) return; //  no JAR file - no version 
        if (!jarToRun.exists()) return; 
        
        try {
            fis = new FileInputStream(jarToRun);
            jis = new JarInputStream(fis);
            
            String ver = null;
            ZipEntry ze;
            while ((ze = jis.getNextEntry()) != null) {
                if ("REVISION".equals(ze.getName())) {
                    ver = readStringFromStream(jis);
                    break;
                }
            }
            if (ver==null) {
                System.err.println("Can not determine JAR version");
                return;
            }

            int idx = ver.lastIndexOf("Revision: ");
            int idx2 = ver.indexOf("\n", idx);
            
            if (idx<0 || idx2<0) {
                System.err.println("Can not parse REVISION file in JAR");
                return;
            }

            ver = ver.substring(idx+10, idx2).trim();
            
            try {
                if (ver!=null) currentVersion = Integer.parseInt(ver);
            } catch (NumberFormatException ex) {  }
            
            startText = "Start JOSM v"+currentVersion;
            System.err.println("Jar version: "+currentVersion); 
        } catch (IOException ex) {
            System.err.println("Can not read JAR file");  
        } finally {
            try {
                if (jis!=null) jis.close();
            } catch (IOException ex) { System.err.println("can not close");  }
            try {
                if (fis!=null) fis.close();
            } catch (IOException ex) { System.err.println("can not close");  }
        }
    }
    
    
    String readStringFromStream(InputStream is) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream(5000);
            byte[] buffer = new byte[32768];
            for (int read = is.read(buffer); read != -1; read = is.read(buffer)) {
                out.write(buffer, 0, read);
            }
            out.close();
            return out.toString();
        } catch (IOException ex) {
            System.err.println("Can not read string from stream");
            return null;
        }
    }

    private boolean checkAlreadyDownloaded() {
        if (fileWithNewVersion.exists()) {
            return renameDownloadedFiles();
        }
        return false;
    }

    private void setupDirectories() {
        String workDir;
        if (portable.isSelected()) {
            workDir = "updates";
        } else {
            // Windows 7, non-admin installation     
            String path = System.getenv("APPDATA");
            if (path != null) {
                workDir = new File(path, "JOSM/updates").getAbsolutePath();
            } else {
                workDir = new File(System.getProperty("user.home"), ".josm/updates").getAbsolutePath();
            }
        }
        
        String name = "josm-"+ (cbTested.getSelectedIndex()==0? "tested" : "latest");
        fileWithNewVersion =  new File(workDir, name+".new");
        fileWithNewVersion.getParentFile().mkdirs();
        jarToRun = new File(workDir, name+".jar");
        backupFile = new File(workDir, name+".old");
        iniFile = new File(workDir, "launcher.ini");
    }

    void setLaunchOnDownload(boolean b) {
        launchOnDownload = b;
    }

    private void downloadIfNeededAndLaunch() {
        if (System.currentTimeMillis() - lastUpdate > updateInterval*24*3600 ) {
            launchOnDownload = true;
            updateJar();
        } else {
            launch();
        }
    }

    
    class GetVersionTask extends SwingWorker<Integer, Void> {
        URLConnection downloadConnection;
        String address;

        int downloadSize;
        int downloadedBytes;

        public GetVersionTask(String address) {
             this.address = address;
        }
        
        @Override
        protected Integer doInBackground() throws Exception {
           try {
                InputStream in;
                URL url = new URL(address);
                downloadConnection = url.openConnection();
                downloadConnection.setRequestProperty("Cache-Control", "no-cache");
                downloadConnection.setRequestProperty("Host", url.getHost());
                downloadConnection.connect();
                downloadSize = downloadConnection.getContentLength();
                in = downloadConnection.getInputStream();
                
                String s = readStringFromStream(in);
                in.close();  
                return Integer.parseInt(s);
            } catch(IOException e) {
                e.printStackTrace();
            }
           return null;
        }
        
    }
    
    class DownloadTask extends SwingWorker<Void, Void> {
        
        URLConnection downloadConnection;
        String address;

        int downloadSize;
        int downloadedBytes;
        private boolean succesful;


        public DownloadTask(String address) {
             this.address = address;
        }
        
        @Override
        protected Void doInBackground() throws Exception {
            OutputStream out = null;
            InputStream in = null;
            succesful = false;

            try {

                URL url = new URL(address);
                downloadConnection = url.openConnection();
                downloadConnection.setRequestProperty("Cache-Control", "no-cache");
                downloadConnection.setRequestProperty("Host", url.getHost());
                downloadConnection.connect();
                downloadSize = downloadConnection.getContentLength();
                if (downloadSize<3000000) {
                    System.err.println("Suspicious JOSM jar size, no download");
                    return null;
                }            

                in = downloadConnection.getInputStream();
                out = new FileOutputStream(fileWithNewVersion);
                byte[] buffer = new byte[32768];
                downloadedBytes=0;
                int p1=0, p2=0;
                for (int read = in.read(buffer); read != -1; read = in.read(buffer)) {
                    out.write(buffer, 0, read);
                    downloadedBytes+=read;
                    if (isCancelled()) {
                        System.err.println("Download cancelled");
                        return null;
                    }
                    
                    p2 = 100 * downloadedBytes / downloadSize;
                    if (p2!=p1) {
                        setProgress(p2);
                        p1=p2;
                    }
                }
            } catch(Exception e) {
                //if (canceled)   return;
                e.printStackTrace();
                return null;
            } finally {
                if (out!=null) try {
                    out.close();
                } catch (IOException ex) {   }
            }
            
            succesful = renameDownloadedFiles();
            return null;
        }

        @Override
        protected void done() {
            setUIState(dt.succesful ? STARTONLY : CHOOSE );
            dt = null;
            if (succesful) {
                lastUpdate = System.currentTimeMillis();
            }
            if (launchOnDownload && succesful) {
                launch();
            }
        }
    }
    
    boolean renameDownloadedFiles() {
        backupFile.delete();
        if (jarToRun.exists()) {
            if (!jarToRun.renameTo(backupFile)) {
                 System.err.println("error renaming");
                 return false;
            }
        }
        if (fileWithNewVersion.exists()) {
            jarToRun.delete();
            if (!fileWithNewVersion.renameTo(jarToRun)) {
                System.err.println("error renaming .new -> .jar");
                return false;
            }
            fileWithNewVersion.delete();
        }
        return true;
    }
    
    void launch() {
        saveProperties();
        try {
            String addOpts = "";
            if (portable.isSelected()) {
                addOpts = String.format("-Djosm.home=\"%s\"", new File(workDir,"data").getAbsolutePath());
            }
            String cmd = String.format("java %s %s -jar \"%s\"", opts.getText(), addOpts, jarToRun.getAbsolutePath());
            Process p = Runtime.getRuntime().exec(cmd);
            Thread.sleep(2000);
            if (p.exitValue()!=0) {
                System.err.println("Error executing!!!");
                jarToRun.delete();
            }
        } catch (Exception ex) {
            System.exit(1);
        }
        
        System.exit(0);
    }

    void center() {
        // Get the size of the screen
        Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();

        // Determine the new location of the window
        int w = getSize().width;
        int h = getSize().height;
        int x = (dim.width-w)/2;
        int y = (dim.height-h)/2;

        // Move the window
        setLocation(x, y);
    }    
    
    
    static final int CHOOSE=0;
    static final int DOWNLOADING=1;
    static final int DOWNLOADONLY=2;
    static final int STARTONLY=3;
    int oldState = 0;
            
    void setUIState(int state) {
        if (oldState!=state && oldState == DOWNLOADING) {
            remove(progress);
            add(start, 3);
            update.setText(updateText);
            validate();  repaint();
        }
        oldState = state;
        
        switch (state) {
        case CHOOSE:
            updateText = "Update to version "+availableVersion;
            start.setVisible(true);
            update.setVisible(true);
            break;
        case STARTONLY:
            startText = "Start JOSM";
            start.setVisible(true);
            update.setVisible(false);
            break;
        case DOWNLOADONLY:
            updateText = "Download JOSM version "+availableVersion;
            update.setVisible(true);
            start.setVisible(false);
            break;
        case DOWNLOADING:
            remove(start);
            add(progress,3);
            updateText = "Cancel download";
            validate();  repaint();
        }
        
        start.setText(startText);
        update.setText(updateText);
    }

    public static void main(String[] args) {
        System.setProperty("java.net.useSystemProxies", "true");
        
        JosmLauncher launcher = new JosmLauncher();
        if (args.length>0) {
            if ("-c".equals(args[0])) {
                // Config option
                launcher.checkFreshVersion(true);
                launcher.setVisible(true);
                return;
            }

            if ("-r".equals(args[0])) {
                // launch Josm
                launcher.launch();
                return;
            }

            if ("-u".equals(args[0])) {
                launcher.setLaunchOnDownload(true);
                launcher.updateJar();
            }
        } else {
            if (launcher.checkAlreadyDownloaded()) {
                launcher.launch();
            } else {
                launcher.downloadIfNeededAndLaunch();
            }
        }
    }
}
