import java.util.ResourceBundle;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JButton;
import javax.swing.BoxLayout;
import javax.swing.JTextArea;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import javax.swing.JOptionPane;
import javax.swing.Box;
import javax.swing.JDialog;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import java.io.File;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;

import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;

import javax.swing.SwingUtilities;

import java.util.Enumeration;
import java.awt.Font;
import javax.swing.plaf.FontUIResource;
import javax.swing.UIManager;



public class Uninstaller {

	public static int SCREEN_WIDTH = 600;
	public static int SCREEN_HEIGHT = 450;

	public static final ResourceBundle bundle = ResourceBundle.getBundle("resources.LanguagePack");

	public static final File gs2InstallProps = new File("etc/installation.properties");
	public static final File gs3InstallProps = new File("installation.properties");

	boolean keepCollections = true;
	//boolean keepModifiedFiles = false;
	boolean ignoreReadOnlys = false;

	JFrame frame;
	JCheckBox keepCollectionsCheckbox;
	//JCheckBox keepModifiedFilesCheckbox;

	//panels
	JPanel progressPanel;
	JPanel introPanel;

	//toolbars	
	JPanel initialToolbar;
	JPanel finishToolbar;


	JScrollPane logPane;
	FollowingJTextArea log;
	JButton uninstallButton;
	JButton finishButton;

	boolean confirmationGiven = false;
	Thread mainThread = null;
	
	public static void main( String[] args ) {
		(new Uninstaller()).go();
	}

	public void go() {

		//set font
		String new_default_font_str = null;
		if ( System.getProperty("os.name").equals("Linux") ) {
			new_default_font_str = "Bitstream Cyberbit";
		} else if ( System.getProperty("os.name").startsWith("Windows") ) {
			new_default_font_str = "Arial Unicode MS";
		}

		if ( new_default_font_str != null ) {

			FontUIResource default_font = new FontUIResource(new_default_font_str, Font.PLAIN, 12);
			Enumeration keys = UIManager.getDefaults().keys();
			while (keys.hasMoreElements()) {
				Object key = keys.nextElement();
				Object value = UIManager.get(key);
				if (value instanceof FontUIResource) {
					UIManager.put(key, default_font);
				}
			}

		}


		mainThread = Thread.currentThread();

		frame =  new JFrame();
		frame.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		frame.setLocation(screenSize.width/2 - frame.getWidth()/2, screenSize.height/2 - frame.getHeight()/2);

		//The panel to introduce and ask for options
		introPanel = new JPanel(new BorderLayout());
		JPanel innerPanel = new JPanel();
		innerPanel.setLayout( new BoxLayout(innerPanel,BoxLayout.Y_AXIS) );
		innerPanel.setBorder( new EmptyBorder(10, 10, 5, 10) );
		introPanel.add( BorderLayout.WEST, innerPanel );

		//The panel to be displayed while the uninstall is happening
		progressPanel = new JPanel(new BorderLayout());
		log = new FollowingJTextArea();
		log.setEditable(false);
		logPane = new JScrollPane(log);
		logPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		logPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		progressPanel.add( BorderLayout.NORTH, new JLabel("Progress") );
		progressPanel.add( BorderLayout.CENTER, logPane );

		//initial toolbar
		initialToolbar = new JPanel();
		uninstallButton = new JButton(bundle.getString("uninstaller.uninstall"));
		uninstallButton.addActionListener( new StartUninstallListener() );
		JButton cancelButton = new JButton(bundle.getString("uninstaller.cancel"));
		cancelButton.addActionListener( new CancelListener() );
		initialToolbar.add(uninstallButton);
		initialToolbar.add(cancelButton);

		//finish toolbar
		finishToolbar = new JPanel();
		finishButton = new JButton(bundle.getString("uninstaller.finish"));
		finishButton.addActionListener( new FinishListener() );
		finishButton.setEnabled( false );
		finishToolbar.add( finishButton );


		String pwd = (new File(".")).getAbsolutePath();
		if ( pwd.endsWith("/.") ) {
			pwd = pwd.substring( 0, pwd.length()-2 );
		}

		JLabel l;		

		l = new JLabel(bundle.getString("uninstaller.will.uninstall.from"));
		innerPanel.add( l );
		innerPanel.add( Box.createRigidArea(new Dimension(5,5)) );

		l = new JLabel("  " + pwd);
		l.setFont( new Font( "Monospaced", Font.BOLD, 14 ) );
		innerPanel.add( l );
		innerPanel.add( Box.createRigidArea(new Dimension(5,20)) );

		l = new JLabel(bundle.getString("uninstaller.uninstall.options"));
		innerPanel.add( l );

		keepCollectionsCheckbox = new JCheckBox(bundle.getString("uninstaller.keep.collections"));
		keepCollectionsCheckbox.setSelected(true);
		innerPanel.add( keepCollectionsCheckbox );

		//keepModifiedFilesCheckbox = new JCheckBox("Keep Modified Files");
		//innerPanel.add( keepModifiedFilesCheckbox );

		frame.setTitle( bundle.getString("uninstaller.greenstone.uninstaller") );
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		//the initial screen
		frame.getContentPane().add( BorderLayout.CENTER, introPanel );
		frame.getContentPane().add( BorderLayout.SOUTH, initialToolbar );

		frame.setVisible(true);
		
		boolean ready = precheck();
		if ( !ready ) {
			System.exit(0);
		}

		//block and wait for signal to do the uninstall
		do {
			try {
				Thread.sleep( Long.MAX_VALUE );
			} catch ( InterruptedException ie ) {
			}
		} while ( !confirmationGiven );

		doUninstall();
		finishButton.setEnabled( true );

	}

	class CancelListener implements ActionListener {
		public void actionPerformed( ActionEvent e ) {
			System.exit(0);
		}
	}

	class FinishListener implements ActionListener {
		public void actionPerformed( ActionEvent e ) {
			System.exit(0);
		}
	}

	class StartUninstallListener implements ActionListener {
		public void actionPerformed( ActionEvent e ) {

			//The dialog to ask "are you sure"
			Object[] options = { bundle.getString("uninstaller.uninstall"), bundle.getString("uninstaller.cancel") };
			int n = JOptionPane.showOptionDialog(
				frame,
				bundle.getString("uninstaller.are.you.sure"),
				bundle.getString("uninstaller.confirmation"),
			    JOptionPane.YES_NO_CANCEL_OPTION,
			    JOptionPane.QUESTION_MESSAGE,
			    null,
			    options,
			    options[0]
			);
			if ( n == 1 ) {
				return;
			}

			keepCollections = keepCollectionsCheckbox.isSelected();
			//keepModifiedFiles = keepModifiedFilesCheckbox.isSelected();

			//confirm delete of collections
			if ( !keepCollections ) {
				options[0] = bundle.getString("uninstaller.continue");
				options[1] = bundle.getString("uninstaller.cancel");
				n = JOptionPane.showOptionDialog(
					frame,
					bundle.getString("uninstaller.are.you.sure.collections"),
					bundle.getString("uninstaller.confirmation"),
				    JOptionPane.YES_NO_CANCEL_OPTION,
				    JOptionPane.WARNING_MESSAGE,
				    null,
				    options,
				    options[0]
				);
				if ( n == 1 ) {
					return;
				}
			}

			
			introPanel.setVisible( false );
			frame.getContentPane().remove(introPanel);
			frame.getContentPane().add( BorderLayout.CENTER, progressPanel );

			initialToolbar.setVisible( false );
			frame.getContentPane().remove( initialToolbar );
			frame.getContentPane().add( BorderLayout.SOUTH, finishToolbar);

			confirmationGiven = true;
			mainThread.interrupt();
		}
	}

	/**
	 * Do some checks
	 */
	public boolean precheck() {

		if ( !gs2InstallProps.exists() && !gs3InstallProps.exists() ) {
			log( bundle.getString("uninstaller.error.couldnt.find.install.props") + "\n" );
			JOptionPane.showMessageDialog(frame, bundle.getString("uninstaller.error.couldnt.find.install.props"), bundle.getString("uninstaller.error"), 0);
			return false;
		}
		return true;
				
	}

	public void log( String s ) {
		SwingUtilities.invokeLater( new LogAppender( s ) );
		//log.append( s );
		
	}

	/**
	 * Does the uninstall
	 */
	public void doUninstall() {

		File startMenuPath = null;

		//delete the start menu group if present

		if ( gs2InstallProps.exists() ) {
			String smp = null;
			try {
				smp = getPropertyValue( "installed.startmenu.path", gs2InstallProps );
			} catch ( Exception e ) {
				System.err.println( e.getMessage() );
			}
			if ( smp != null ) {
				startMenuPath = new File( smp );
			}
		} else if ( gs3InstallProps.exists() ) {
			String smp = null;
			try {
				smp = getPropertyValue( "installed.startmenu.path", gs3InstallProps );
			} catch ( Exception e ) {
				System.err.println( e.getMessage() );
			}
			if ( smp != null ) {
				startMenuPath = new File( smp );
			}
		}

		if ( startMenuPath == null ) {

			log( bundle.getString("uninstaller.info.no.startmenu") + "\n" );

		} else {
			log( "StartMenu Path: " + startMenuPath.getAbsolutePath() + "\n" );

			try {
				recursiveDelete( startMenuPath, null );
			} catch ( CancelledException ce ) {
				log( bundle.getString("uninstaller.cancelled") + "\n" );
				changeToFinishToolbar();
				JOptionPane.showMessageDialog(frame, bundle.getString("uninstaller.cancelled"), bundle.getString("uninstaller.complete"), 1);
				return;
			}
		}

		//delete the files
		try {
			ArrayList exceptions = new ArrayList();

			//never delete the things we are currently running
			exceptions.add( new File("bin/search4j.exe") );
			exceptions.add( new File("bin/search4j") );

			exceptions.add( new File("bin/windows/search4j.exe") );
			exceptions.add( new File("bin/linux/search4j") );
			exceptions.add( new File("bin/darwin/search4j") );

            // Uninstall.sh had renamed selfcontained-jdk64/zulu-jdk11-64 to selfcontained-jdk/jdk
			exceptions.add( new File("selfcontained-jdk/jdk") ); //exceptions.add( new File("packages/jre") );
			exceptions.add( new File("uninstall") );

			if ( keepCollections ) {
				exceptions.add( new File("web/sites/localsite/collect") );
				exceptions.add( new File("collect") );
			}


			File cd = null;
			try {
				cd = new File( new File(".").getCanonicalPath() );
			} catch ( Exception e ) {
				JOptionPane.showMessageDialog(frame, bundle.getString("uninstaller.failed.to.figure.cd"), bundle.getString("uninstaller.error") , 0);
				System.exit(0);
			}
			
			File[] ex = new File[exceptions.size()];
			for ( int i=0; i<exceptions.size(); i++ ) {
				ex[i] = (File)exceptions.get(i);
			}
			
			//recursiveDelete( cd , ex );
			selectiveDelete ( cd, ex );
		} catch ( CancelledException ce ) {
			log( bundle.getString("uninstaller.cancelled") + "\n" );
			JOptionPane.showMessageDialog(frame, bundle.getString("uninstaller.cancelled"), bundle.getString("uninstaller.complete"), 1);
			changeToFinishToolbar();
			return;
		}

		//create the flag file to indicate the uninstaller wants jre and uninst.jar to be deleted
		try {
			(new File("uninst.flag")).createNewFile();
		} catch (Exception e) {
			log( bundle.getString("uninstaller.couldnt-create-flagfile") + "\n" );
		}

		changeToFinishToolbar();
		log( bundle.getString("uninstaller.finished") );
		JOptionPane.showMessageDialog(frame, bundle.getString("uninstaller.finished"), bundle.getString("uninstaller.complete"), 1);
	}

	class LogAppender implements Runnable {
		String s;

		public LogAppender( String s ) {
			this.s = s;
		}

		public void run() {
			log.append( s );
		}

	}

	public String getPropertyValue( String propertyName, File file ) throws Exception {

		String regex = "^" + propertyName.replaceAll("\\.","\\\\.") + "[:=]\\s*(.*)$";
		Pattern wantedLine = Pattern.compile( regex );
		BufferedReader in = null;
		try {
			in = new BufferedReader(new FileReader( file ));
		} catch ( Exception e ) {
			throw new Exception( "Error - couldn't open the properties file " + file );
		}

		String line, value = null;

		try {
			boolean found = false;
			while ( (line = in.readLine()) != null && !found ) {

				Matcher matcher = wantedLine.matcher( line );
				if ( matcher.matches() ) {
					//found the property
					//System.err.println( "found the property" );
					value = matcher.group(1);
					value = value.replaceAll( "#.*", "" ).trim();
					return value;
				}

			}

			//close the input stream
			//System.err.println( "close the input stream" );
			in.close();

		} catch ( Exception e ) {
			throw new Exception( "Error - couldn't read from the properties file" );
		}
		return null;

	}
	
	public void selectiveDelete( File f, File[] exceptions ) throws CancelledException {
		
		File[] files = (new File("uninstall")).listFiles();
		
		for ( int i=0; i < files.length; i++) {
			if( files[i].getAbsolutePath().endsWith(".uninstall") ) {
				String[] paths = getPathsFromUninstallFile(files[i]);
				
				for(int j=0; j < paths.length; j++) {
					recursiveDelete( new File(paths[j]), exceptions );
				}
			}
		}
	}
	
	public String[] getRelevantPathsFromUninstallFile ( File uninstallFile ) {
		
		ArrayList paths = new ArrayList();
		try {
			BufferedReader in = new BufferedReader(new FileReader(uninstallFile));
			
			String line;
			while ( (line = in.readLine()) != null ) {
				int separatorIndex = line.indexOf(File.separator);
				
				if ( separatorIndex == -1 && line.length() > 0 && !paths.contains(line)) {
					paths.add(line);
				}
				else if ( separatorIndex > -1 && line.length() > 0) {
					String path = line.substring(0, separatorIndex);
					if ( !paths.contains(path) ) {
						paths.add(path);
					}
				}
			}
			
			in.close();
		}
		catch( Exception ex ) {
			ex.printStackTrace();
			return null;
		}
		return (String[]) paths.toArray(new String[0]);
	}
	
	public String[] getPathsFromUninstallFile ( File uninstallFile ) {
		
		ArrayList paths = new ArrayList();
		try {
			BufferedReader in = new BufferedReader(new FileReader(uninstallFile));
			
			String line;
			while ( (line = in.readLine()) != null ) {
				if (line.length() > 0 && !paths.contains(line)) {
					paths.add(line);
				}
			}
			
			in.close();
		}
		catch( Exception ex ) {
			ex.printStackTrace();
			return null;
		}
		return (String[]) paths.toArray(new String[0]);
	}

	public void recursiveDelete( File f, File[] exceptions ) throws CancelledException {

		//log.append( "Processing: " + f.getAbsolutePath() + "\n" );

		// if this is an exception, return
		if ( exceptions != null ) {
			for ( int i=0; i<exceptions.length; i++ ) {
				//System.out.println( "Comparing '" + f.getAbsolutePath() + "' with '" + exceptions[i].getAbsolutePath() + "'" );
				try {
					if ( f.equals( exceptions[i] ) || f.getCanonicalPath().equals(exceptions[i].getCanonicalPath()) ) {
						log( Strings.replaceAll( bundle.getString("uninstaller.info.skipping"), "{file}", f.getAbsolutePath() ) + "\n" );
						return;
					}
				} catch ( Exception e ) {
					System.err.println("ERROR: Failed to resolve a path");
					return;
				}
			}
		}

		//if it is a directory, recurse
		if (f.isDirectory()) {
			File[] files = f.listFiles();
			if ( files != null && files.length > 0) {
				for ( int i=0; i<files.length; i++ ) {
					try {
						recursiveDelete( files[i], exceptions );
					} catch ( CancelledException ce ) {
						throw new CancelledException();
					}
				}
			}
		}

		//if this is now an empty directory, or a file, delete it
		boolean doDelete = true;
		if ( f.isDirectory() ) {
			File[] files = f.listFiles();
			doDelete = ( files == null || files.length == 0 );
		}
		
		if ( doDelete ) {

			log( Strings.replaceAll( bundle.getString("uninstaller.deleting"), "{file}", f.getAbsolutePath() ) + "\n" );

			if ( !f.delete() ) {
				log( "*********\n" + Strings.replaceAll( bundle.getString("uninstaller.warning.couldnt.delete"), "{file}", f.getAbsolutePath() ) + "\n*********\n" );
				return;
			}
		}

	}

	class CancelledException extends Exception {}

	public void changeToFinishToolbar() {
		initialToolbar.setVisible(false);
		frame.getContentPane().remove(initialToolbar);
		frame.getContentPane().add( BorderLayout.SOUTH, finishToolbar );
		finishToolbar.setVisible(true);
	}

}

class Strings {
	static String replaceAll( String s, String nugget, String replacement ) {
		StringBuffer string = new StringBuffer(s);
		StringBuffer r = new StringBuffer();

		int io = 0;
		while ( (io=string.toString().indexOf(nugget)) != -1 ) {
			r.append( string.toString().substring( 0, io ) );
			r.append( replacement );
			string.delete( 0, io + nugget.length() );
		}
		r.append( string.toString() );
		return r.toString();
	}
}

class FollowingJTextArea extends JTextArea {

    private boolean follow = true;

    public FollowingJTextArea() {
        jInit();
    }


    private void jInit(){
        final JPopupMenu popUp = getPopupMenu();
        this.add(popUp);
        this.addMouseListener(new MouseAdapter(){
            public void mouseClicked(MouseEvent e) {
                if (e.getButton() == e.BUTTON3) {
                    popUp.show(FollowingJTextArea.this,e.getX(),e.getY());
                }
            }
        });
    }

    public boolean isFollow() {
        return follow;
    }
    public void setFollow(boolean follow) {
        this.follow = follow;
    }

    private void scrollToEnd(){
        setCaretPosition(getDocument().getLength());
    }
    private void toggleFollow(){
        setFollow(!isFollow());
    }

    /**
     * Appends the given text to the end of the document.
     *
     * @param str the text to insert
     * @todo Implement this javax.swing.JTextArea method
     */
    public void append(String str) {
        super.append(str);
        if(follow)scrollToEnd();
    }
    private JPopupMenu getPopupMenu() {
        JPopupMenu contextMenu = new JPopupMenu("Options");

        JMenuItem toggleFollowMenu = new JMenuItem("Toggle Follow");
        toggleFollowMenu.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                toggleFollow();
            }
        });
        contextMenu.add(toggleFollowMenu);

        JMenuItem jumpToEndMenu = new JMenuItem("Jump To End");
        jumpToEndMenu.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setCaretPosition(getDocument().getLength());
            }
        });
        contextMenu.add(toggleFollowMenu);

        JMenuItem clearTextMenu = new JMenuItem("Clear Text");
        clearTextMenu.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                setText("");
            }
        });
        contextMenu.add(clearTextMenu);
        return contextMenu;
    }
}
