import java.io.*;

public class Replace {
	private static boolean verbose = false;
	private static boolean insensitive = false;

	private static String pattern1;
	private static String pattern2;

	private static int patternLength;

	private static final String UNIQUE_STRING = "``~~!!@@##$$%%^^&&**(()__++==";

    public static void main( String[] args ) {
		// NB. The JVM automatically converts filenames containing wildcards into a list of filenames. So
		//     when N files match the filename expression, it is as if you typed those N filenames explicitly
		//     in the commandline.

		// Proces input.

		if (args.length < 3)
		{
			printHelp();
			System.exit(0);
		}

		int offset = 0;
		if ( args[0].startsWith("-") )
		{
			offset = 1;

			if (args.length < 4)
			{
				printHelp();
				System.exit(0);
			}
		}

		if (offset > 0)
		{
			if (args[0].indexOf("i") != -1) {insensitive = true;}
			if (args[0].indexOf("v") != -1) {verbose = true;}
		}

		pattern1 = args[offset];
		pattern2 = args[offset + 1];

		if (verbose)
		{
			System.out.println(   "\nOriginal patterns:"
								+ "\n<pattern1> = \"" + pattern1 + "\""
								+ "\n<pattern2> = \"" + pattern2 + "\"" );
		}

		patternLength = pattern1.length(); // Calculate this only once.
		if (insensitive) {pattern1 = pattern1.toLowerCase();}

		// Validate input.

		if ( patternLength == 0 )
		{
			System.out.println("Error: <pattern1> must not be empty.");
			System.exit(0);
		}

		boolean recurrent = false;
		if (pattern2.indexOf(pattern1) > -1) {recurrent = true;}
		if ( (insensitive) && (pattern2.toLowerCase().indexOf(pattern1) > -1) ) {recurrent = true;}
		if (recurrent)
		{
			System.out.println("Error: <pattern2> must not contain <pattern1>.");
			System.exit(0);
		}

		// Unstuff patterns because of possible character stuffing.

		pattern1 = unstuff(pattern1);
		pattern2 = unstuff(pattern2);

		if (verbose)
		{
			System.out.println(   "\nPreprocessed patterns:"
								+ "\n<pattern1> = \"" + pattern1 + "\""
								+ "\n<pattern2> = \"" + pattern2 + "\"" );
		}

		if ( pattern1.indexOf("\n") > -1 )
		{
			System.out.println("Error: <pattern1> must not contain \\n.");
			System.exit(0);
		}

		// Actual processing.

		for (int i = offset + 2; i < args.length; i++) {processFile(args[i]);}

		if (verbose) {System.out.println( "#files processed = " + (args.length - 2 - offset) );}

		System.exit(0);
	}

	private static void processFile(String aFilename)
	{
		String filename = aFilename;

		if (verbose) {System.out.print(filename + " : ");}

		String foutFilename = filename + ".tmp";

		File fin  = new File(filename);
		File fout = new File(foutFilename);

		String line;
		StringBuffer buf;
		int index;

		if ( fin.exists() )
		{
			try
			{
				if ( fout.exists() )
				{
					System.out.println("Error: Existence of file: " + foutFilename + " hinders execution.");
					return;
				}
				else
				{
					fout.createNewFile();
				}
			}
			catch (Exception e)
			{
				System.out.println( "Error: Unable to create temporary file: " + foutFilename + "\n Exception: " + e.toString() );
				return;
			}

			try
			{
				BufferedReader in = new BufferedReader( new FileReader(fin) );
				PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter(fout)) );

				line = in.readLine();
				while (line != null)
				{
					buf = new StringBuffer( line );

					if (insensitive) {index = buf.toString().toLowerCase().indexOf(pattern1);}
					else {index = buf.toString().indexOf(pattern1);}

					while (index > -1)
					{
						buf.replace(index, index + patternLength, pattern2);

						if (insensitive) {index = buf.toString().toLowerCase().indexOf(pattern1);}
						else {index = buf.toString().indexOf(pattern1);}
					}
					out.println( buf.toString() );

					line = in.readLine();
				}

				in.close();
				out.close();

				fin.delete();
				fout.renameTo(fin);
			}
			catch (Exception e)
			{
				System.out.println( "Error: " + e.toString() );
				return;
			}
		}
		else
		{
			System.out.println("Error: File not found:" + fin.getName());
			return;
		}

		if (verbose) {System.out.println("OK");}
	}

	private static void printHelp()
	{
		System.out.println(   "Replace, version 1.0, by Ronald Koster (http://home.wanadoo.nl/ronald.koster)\n\n"
							+ "A simplified sed (stream editor) version.\n\n"
							+ "Usage: java Replace [-iv] <patern1> <patern2> <file1> [file2 ...]\n\n"
							+ "<patern1>   : pattern to be replaced\n"
							+ "<patern2>   : new pattern\n"
							+ "<file1>     : filename of text file to operate on\n"
							+ "[file2 ...] : optional additional filenames\n\n"
							+ "Options\n\n"
							+ "  i     : match <pattern1> case insensitive\n"
							+ "  v     : verbose output option\n\n"
							+ "NB. + Patterns may optionally be enclosed by \".\n"
							+ "    + They must be enclosed by \" when they contain spaces.\n"
							+ "    + They must be literal string patterns. No regular or any other expressions are supported.\n"
							+ "    + The following escape sequences are supported:\n"
							+ "        \\\" : \" \n"
							+ "        \\n : NEWLINE \n"
							+ "        \\\\ : \\ \n"
							+ "    + NEWLINE is not allowed in <pattern1>.\n"
							+ "    + <pattern1> may start with a - in verbose mode only.\n\n"
							+ "    + If the last line of a file touched does not end with a NEWLINE, one is appended." );
	}

	private static String unstuff(String aPattern)
	{
		if (aPattern.indexOf(UNIQUE_STRING) > -1)
		{
			System.out.println("Error: Pattern must not contain: " + UNIQUE_STRING);
			System.exit(0);
		}

		// First replace all \\ with UNIQUE_STRING.
		aPattern = replace("\\\\", UNIQUE_STRING, aPattern);
		// Second replace all \n with NEWLINE.
		aPattern = replace("\\n", "\n", aPattern);
		// Third relace all UNIQUE_STRING with \.
		aPattern = replace(UNIQUE_STRING, "\\", aPattern);

		return aPattern;
	}

	private static String replace(String aPat1, String aPat2, String aPattern)
	{
		StringBuffer pattern = new StringBuffer(aPattern);

		int index = pattern.indexOf(aPat1);
		while (index > -1)
		{
			pattern.replace(index, index + aPat1.length(), aPat2);
			index = pattern.indexOf(aPat1);
		}
		return pattern.toString();
	}
}

