// ResAsm.cs
// Copyright (c) 2001 Mike Krueger
//
// 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

using System;
using System.Collections;
using System.Drawing;
using System.Resources;
using System.IO;
using System.Text;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Runtime.Serialization.Formatters.Binary;

/// <summary>
/// This tool is written for SharpDevelop to have a runtime independend
/// format for Resource Files. Microsoft did break the resx and resource
/// file format during the beta, this tool helped a lot to translate the
/// resource files from version to version. 
/// Could be used in any .NET project to provide a runtime independend
/// format (useful for translating resources between mono and ms .net)
/// </summary>
public class ResAsm
{
	/// <remarks>
	/// Builds ResAsm files out of resource files
	/// </remarks>
	static void Disassemble(string pattern)
	{
		string[] files = Directory.GetFiles(Directory.GetCurrentDirectory(), pattern);
		foreach (string file in files) {
			
			Hashtable resources = new Hashtable();
			int       length    = 0;
			// read resource files into the hashtable
			switch (Path.GetExtension(file).ToUpper()) {
				case ".RESX":
					ResXResourceReader rx = new ResXResourceReader(file);
					IDictionaryEnumerator n = rx.GetEnumerator();
					while (n.MoveNext()) 
						if (!resources.ContainsKey(n.Key)) {
							length = Math.Max(length, n.Key.ToString().Length);
							resources.Add(n.Key, n.Value);
						}
					
					rx.Close();
				break;
				case ".RESOURCES":
					ResourceReader rr = new ResourceReader(file);
					foreach (DictionaryEntry entry in rr) {
						if (!resources.ContainsKey(entry.Key)) {
							length = Math.Max(length, entry.Key.ToString().Length);
							resources.Add(entry.Key, entry.Value);
						}
					}
					rr.Close();
				break;
			}
			
			// write the hashtable to the resource file
			string fname  = Path.GetFileNameWithoutExtension(file);
			string path   = fname + "-data";
			StreamWriter writer = File.CreateText(fname + ".res");
			
			writer.Write("# this file was automatically generated by ResAsm\r\n\r\n");
			foreach (DictionaryEntry entry in resources) {
				// strings are put directly into the resasm format
				if (entry.Value is string) {
					writer.Write(entry.Key.ToString() + "=\"" + ConvertIllegalChars(entry.Value.ToString()) + "\"\r\n");
				} else {
					// all other files are referenced as a file and the filename
					// is saved in the resasm format, the files need to be generated.
					string extension  = "";
					string outputname = path + '\\' + entry.Key.ToString();
					if (entry.Value is Icon) {
						extension = ".ico";
						if (!Directory.Exists(path))
							Directory.CreateDirectory(path);
						((Icon)entry.Value).Save(File.Create(outputname + extension));
					} else if (entry.Value is Image) {
						// all bitmaps are saved in the png format
						extension = ".png";
						if (!Directory.Exists(path))
							Directory.CreateDirectory(path);
						((Image)entry.Value).Save(outputname + extension, ImageFormat.Png);
					} else {
						Console.WriteLine("can't save " + entry.Key + " unknown format.");
						continue;
					}
					writer.Write(entry.Key.ToString().PadRight(length) + " = " + outputname + extension + "\r\n");
				}
			}
			writer.Close();
		}
	}
	
	static string ConvertIllegalChars(string str)
	{
		StringBuilder newString = new StringBuilder();
		for (int i = 0; i < str.Length; ++i) {
			switch (str[i]) {
				case '\r':
					break;
				case '\n':
					newString.Append("\\n");
					break;
				case '"':
					newString.Append("\\\"");
					break;
				case '\\':
					newString.Append("\\\\");
					break;
				default:
					newString.Append(str[i]);
					break;
			}
		}
		return newString.ToString();
	}
	
	/// <remarks>
	/// Builds resource files out of the ResAsm format
	/// </remarks>
	static int Assemble(string pattern)
	{
		string[] files = Directory.GetFiles(Directory.GetCurrentDirectory(), pattern);
		if (files.Length == 0) {
			Console.WriteLine("'" + pattern + "' not found.");
			return 1;
		}
		int linenr = 0;
		foreach (string file in files) {
			try {
				StreamReader reader      = new StreamReader(file, new UTF8Encoding());
				string       resfilename = Path.GetFileNameWithoutExtension(file) + ".resources";
				
				ResourceWriter rw = new ResourceWriter(resfilename);
				linenr = 0;
				while (true) {
					string line = reader.ReadLine();
					linenr++;
					if (line == null) {
						break;
					}
					line = line.Trim();
					// skip empty or comment lines
					if (line.Length == 0 || line[0] == '#') {
						continue;
					}
					
					// search for a = char
					int idx = line.IndexOf('=');
					if (idx < 0) {
						Console.WriteLine("error in file " + file + " at line " + linenr);
						return 1;
					}
					string key = line.Substring(0, idx).Trim();
					string val = line.Substring(idx + 1).Trim();
					object entryval = null;
					
					if (val[0] == '"') { // case 1 : string value 
						val = val.Trim(new char[] {'"'});
						StringBuilder tmp = new StringBuilder();
						for (int i = 0; i < val.Length; ++i) {
							switch (val[i]) { // handle the \ char 
								case '\\':
										++i;
										if (i < val.Length)
										switch (val[i]) {
											case '\\':
												tmp.Append('\\');
												break;
											case 'n':
												tmp.Append('\n');
												break;
											case '\"':
												tmp.Append('\"');
												break;
										}
										break;
								default:
									tmp.Append(val[i]);
									break;
							}
						}
						entryval = tmp.ToString();
					} else { // case 2 : no string value -> load resource
						entryval = LoadResource(val);
					}
					rw.AddResource(key, entryval);
					
				}
				rw.Generate();
				rw.Close();
				reader.Close();
			} catch (Exception e) {
				Console.WriteLine("Error in line " + linenr);
				Console.WriteLine("Error while processing " + file + " :");
				Console.WriteLine(e.ToString());
				return 1;
			}
		}
		return 0;
	}
	
	/// <remarks>
	/// Loads a file. 
	/// </remarks>
	/// <returns>
	/// An object representation of the file (for a bitmap a Bitmap,
	/// for a Icon an Icon and so on), the fall back is a byte array
	/// </returns>
	static object LoadResource(string name)
	{
		switch (Path.GetExtension(name).ToUpper()) {
			case ".CUR":
				return new Cursor(name);
			case ".ICO":
				return new Icon(name);
			default:
				// try to read a bitmap
				try { 
					return new Bitmap(name); 
				} catch {}
				
				// try to read a serialized object
				try {
					Stream r = File.Open(name, FileMode.Open);
					try {
						BinaryFormatter c = new BinaryFormatter();
						object o = c.Deserialize(r);
						r.Close();
						return o;
					} catch { r.Close(); }
				} catch { }
				
				// finally try to read a byte array
				try {
					FileStream s = new FileStream(name, FileMode.Open);
					BinaryReader r = new BinaryReader(s);
					Byte[] d = new Byte[(int) s.Length];
					d = r.ReadBytes((int) s.Length);
					s.Close();
					return d;
				} catch (Exception e) { 
					MessageBox.Show(e.Message, "Can't load resource", MessageBoxButtons.OK); 
				}
			break;
		}
		return null;
	}
	
	/// <remarks>
	/// Prints help about the ResAsm format.
	/// </remarks>
	static void ShowInfo()
	{
		Console.WriteLine("This tool converts .resource or .resx files to an own format, it helps to");
		Console.WriteLine("port these files to a new .NET version.");
		Console.WriteLine("It can also be used as a resource assembler.\n");
		Console.WriteLine("INPUT FORMAT OF THE TEXT FILE :\n");
		Console.WriteLine("               #<COMMENT>");
		Console.WriteLine("               <key>=<value>\n");
		Console.WriteLine("               <value> can be :");
		Console.WriteLine("                     <value> = \"<STRINGVALUE>\"");
		Console.WriteLine("                             OR");
		Console.WriteLine("                     <value> = <FILENAME>\n");
		Console.WriteLine("The file can be : A Bitmap, Icon, Cursor or serialized object, otherwise");
		Console.WriteLine("the file will be interpreted as an byte array");
	}
	
	/// <remarks>
	/// Prints help about the command line arguments.
	/// </remarks>
	static void ShowHelp()
	{
		Console.WriteLine(".NET Resource Assembler Version 0.1");
		Console.WriteLine("Copyright (C) Mike Krueger 2001. Released under GPL.\n");
		Console.WriteLine("                      Resource Assembler Options\n");
		Console.WriteLine("                        - INPUT FILES -");
		Console.WriteLine("/d:<wildcard>           Disassembles the .resource or .resx files and generates");
		Console.WriteLine("                        .res files.");
		Console.WriteLine("                        - HELP -");
		Console.WriteLine("/?                      Displays this help.");
		Console.WriteLine("/INFO                   Gives additional information about ResAsm.");
	}
	
	/// <remarks>
	/// The main function.
	/// </remarks>
	public static int Main(string[] args)
	{
		if (args.Length == 0) {
			ShowHelp();
		}
		// try to find help or info params
		foreach (string param in args) {
			string par = param.ToUpper();
			if (par == "/INFO") {
				ShowInfo();
				return 0;
			}
				
			if (par == "/?" || par == "/H" || par== "-?" || par == "-H" || par == "?") {
				ShowHelp();
				return 0;
			}
		}
		
		// no help or info param found, goto the action
		foreach (string param in args) {
			if (param.StartsWith("/d:")) {
				Disassemble(param.Substring(3));
			} else {
				int res = Assemble(param);
				if (res != 0)
					return res;
			}
		}
		return 0;
	}
}
