/*
 *    RegexSearchReplace.java
 *    A task to replace occurences of a given pattern with a given replacement in a given file
 *
 *    Copyright (C) 2005 New Zealand Digital Library, http://www.nzdl.org
 *
 *    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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package org.greenstone.anttasks;

import java.util.ArrayList;
import org.apache.tools.ant.*;
import org.apache.tools.ant.taskdefs.*;
import java.util.*;
import java.io.*;
import java.util.regex.*;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.DirectoryScanner;

public class RegexSearchReplace extends Task {

	private File file = null;
	private ArrayList jobs = null;
	private FileSet fileset = null;
	private String pattern = null;
	private String replacement = null;
	private int verbosity = 1;
	private int fromLine = -1;
	private int toLine = -1;
	private boolean winPath = false;

	public static void main( String[] args ) {

		RegexSearchReplace rsr = new RegexSearchReplace();
		rsr.setFile(new File (args[0]));
		rsr.setPattern(args[1]);
		rsr.setReplacement(args[2]);
		rsr.setLines(args[3]);

		rsr.execute();

	}

	public void execute() {

		if ( file == null && fileset == null ) {
			throw new BuildException( "Error - No file or fileset specified !!" );
		}

		if ( file != null && !file.exists() ) {
			throw new BuildException( "Error - File not found !!" );
		}

		DirectoryScanner dirScanner = null;
		if ( fileset != null ) {
			dirScanner = fileset.getDirectoryScanner(project);
			dirScanner.scan();
		}

		if ( jobs == null && pattern == null ) {
			throw new BuildException( "Error - No pattern attribute and no nested jobs !!" );
		}

		if ( jobs != null && pattern != null ) {
			throw new BuildException( "Error - Both pattern attribute and nested jobs given !!" );
		}

		if ( pattern != null && replacement == null ) {
			throw new BuildException( "Error - pattern attribute given but no replacement attribute given !!" );
		}

		if ( jobs != null ) {
			for ( int i=0; i<jobs.size(); i++ ) {
				if ( ((RegexSearchReplaceJob)(jobs.get(i))).getPattern() == null ) {
					throw new BuildException( "Error - One of the jobs lacks a pattern!!" );
				}
				if ( ((RegexSearchReplaceJob)(jobs.get(i))).getReplacement() == null ) {
					throw new BuildException( "Error - One of the jobs lacks a replacement!!" );
				}
			}
		}


		//sus out filename(s)
		File[] files = null;
		if ( file != null ) {
			files = new File[1];
			files[0] = file;
		} else {
			String[] fileStrings = dirScanner.getIncludedFiles();
			files = new File[fileStrings.length];
			for ( int i=0; i<fileStrings.length; i++ ) {
				files[i] = new File( dirScanner.getBasedir() + File.separator + fileStrings[i] );
			}
		}

		//output greeting
		if (verbosity >= 3) {
		    System.out.println( "--------------------" );
		    System.out.println( " RegexSearchReplace " );
		    System.out.println( "--------------------" );


		    //output replacement(s)
		    System.out.println( "Replacements:" );

		    if ( pattern != null ) {
			System.out.println( " " + pattern + " -> " + replacement );
		    } else {
			for ( int i=0; i<jobs.size(); i++ ) {
				System.out.println( " " + ((RegexSearchReplaceJob)(jobs.get(i))).getPattern() + " -> " + ((RegexSearchReplaceJob)(jobs.get(i))).getReplacement() );
			}
		    }
		    System.out.println();


		    //output lines if necessary
		    if ( this.fromLine != -1 || this.toLine != -1 ) {
			System.out.println( "Lines: " + (this.fromLine==-1?"Start":Integer.toString(this.fromLine)) + " ~ " + (this.toLine==-1?"End":Integer.toString(this.toLine)) );
			System.out.println();
		    }
		}

		if (verbosity == 1) {
		    if (files.length == 1) {
			System.out.println("Regex Search and Replace on file: ");
		    }
		    else {
			System.out.println("Regex Search and Replace on files: ");
		    }
		}
		else if (verbosity > 1) {
		    System.out.println( "File(s): " );
		}


		for ( int fileIndex=0; fileIndex<files.length; fileIndex++ ) {

			int noReplaces = 0;

			File inputFile = files[fileIndex];
			if (verbosity >= 1) {
			    System.out.println( " " + inputFile );
			}

			//create the output stream
			BufferedWriter out = null;
			File temp = null;
			try {
				//create temp file.
				temp = File.createTempFile("rsr", ".tmp");

				//delete temp file when program exits.
				temp.deleteOnExit();

				//writer to temp file
				out = new BufferedWriter( new OutputStreamWriter(new FileOutputStream(temp), "UTF8") );

			} catch (IOException e) {
				throw new BuildException( "Error - Couldn't create or open the temp file" );
			}


			//create the input stream
			BufferedReader in = null;
			try {
				in = new BufferedReader( new InputStreamReader(new FileInputStream(inputFile), "UTF8") );
			} catch ( Exception e ) {
				throw new BuildException( "Error - Couldn't open the specified file" );
			}

			//pass the file through, searching and replacing
			String line = null;
			int lineNumber = 1;
			boolean hasMoreLines = true;
			while ( hasMoreLines ) {

				try {
					line = in.readLine();
				} catch ( Exception e ) {
					System.err.println( e.getMessage() );
					throw new BuildException( "Error - Couldn't read from the specified file" );
				}

				if ( line == null ) {
					hasMoreLines = false;
				} else {

					//only if within the given line range
					if ( ( fromLine == -1 || lineNumber >= fromLine ) && ( toLine == -1 || lineNumber < toLine ) ) {
						String oldLine = line;
						if ( pattern != null ) {
							String rp = replacement;
							if ( winPath ) {
								rp = rp.replaceAll("\\\\","\\\\\\\\");
							}
							line = line.replaceAll((String)pattern, (String)rp);
						} else {
							for ( int i=0; i<jobs.size(); i++ ) {
								String rp = ((RegexSearchReplaceJob)(jobs.get(i))).getReplacement();
								if ( ((RegexSearchReplaceJob)(jobs.get(i))).getWinPath() ) {
									rp = rp.replaceAll("\\\\","\\\\\\\\");
								}
								line = line.replaceAll( ((RegexSearchReplaceJob)(jobs.get(i))).getPattern(), rp );
							}
						}

						if ( !oldLine.equals( line ) ) {
							noReplaces++;
						}
					}

					try {
						out.write( line );
						out.newLine();
					} catch ( Exception e ) {
						throw new BuildException( "Error - Couldn't write to the temp file" );
					}
				}
				lineNumber++;
			}
			
			try {
				//close them both up
				in.close();
				out.close();
			} catch ( Exception e ) {
				throw new BuildException( "Error - Couldn't close a file" );
			}


			//copy the new file (temp) over the original
			InputStream i;
			OutputStream o;
			try {
				i = new FileInputStream(temp);
				o = new FileOutputStream(inputFile);

			} catch ( Exception e ) {
				throw new BuildException( "Error - Couldn't open the temp file" );
			}

			try {
		
				// Transfer bytes from in to out
				byte[] buf = new byte[1024];
				int len;
				while ((len = i.read(buf)) > 0) {
				   o.write(buf, 0, len);
				}

				//close them up
				i.close();
				o.close();
			} catch ( Exception e ) {
				throw new BuildException( "Error - Couldn't write to the specified file" );
			}

		
			if (verbosity >= 2) {
			    if ( noReplaces == 0 ) {
				System.out.println( " No Changes Made" );
			    } else if ( noReplaces == 1 ) {
				System.out.println( " Successfully changed 1 line" );
			    } else {
				System.out.println( " Successfully changed " + noReplaces + " lines" );
			    }
			    System.out.println();
			}

		}

	}

	public void setFile(File file) {
		if ( this.fileset != null || this.file != null ) {
			throw new BuildException( "Error - Only one file or one fileset may be given!!" );
		}
		this.file = file;
	}

	public void setLines(String lines) {
		//lines should be in the form "2" or "2-5"

		//trim
		lines.replaceAll("\\s","");

		if ( lines == "" ) {
			throw new BuildException( "Error - no line number(s) given in lines attribute!! " );
		}

		//split into parts
		String[] parts = lines.split("-",2);

		try {
			if ( parts.length == 1 ) {
				this.fromLine = Integer.parseInt( parts[0] );
				this.toLine = this.fromLine + 1;
			} else {
				if ( !parts[0].equals("") ) this.fromLine = Integer.parseInt( parts[0] );
				if ( !parts[1].equals("") ) this.toLine = Integer.parseInt( parts[1] );
			}
		} catch( NumberFormatException nfe ) {
			throw new BuildException( "Error - invalid line numbers given in lines attribute!! '" + parts[0] + "' - '" + parts[1] + "'" );
		}

	}

	public void setVerbosity(String verbosity_str) {

		//trim
		verbosity_str.replaceAll("\\s","");

		if (verbosity_str == "" ) {
			throw new BuildException("Error - no value found in 'verbosity' attribute!");
		}

		try {
		    this.verbosity = Integer.parseInt(verbosity_str);
		} catch( NumberFormatException nfe ) {
			throw new BuildException("Error - invalid line numbers given in 'verbosity' attribute! '" 
						 + verbosity_str + "'" );
		}

	}

	public void setPattern(String pattern) {
		this.pattern = pattern;
	}

	public void setReplacement(String replacement) {
		replacement = replacement.replaceAll( "\\\\", "\\\\\\\\" );
		this.replacement = replacement;
	}

	public void setWinPath( boolean isWinPath ) {
		this.winPath = isWinPath;
	}

	public RegexSearchReplaceJob createJob() {
		RegexSearchReplaceJob job = new RegexSearchReplaceJob();
		if ( jobs == null ) {
			jobs = new ArrayList();
		}
		jobs.add( job );
		return job;

	}

	public FileSet createFileset() {
		if ( this.fileset != null || this.file != null ) {
			throw new BuildException( "Error - Only one file or one fileset may be given!!" );
		}
		fileset = new FileSet();
		return fileset;
	}

}
