// Copyright (C) 2009 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Moonlight.SecurityModel;

class Program {

	static List<string> additions = new List<string> ();
	static List<string> removals = new List<string> ();
	static List<string> always = new List<string> ();

	private static List<string> Merge ()
	{
		List<string> lines = new List<string> ();

		foreach (string add in additions) {
			// check if this addition was 'manually' overidden
			if (removals.Contains (add))
				continue;

			if (!lines.Contains (add))
				lines.Add (add);
		}

		foreach (string add in always) {
			// add '!' into the list (if they were not already there)
			if (!lines.Contains (add))
				lines.Add (add);
		}

		// simplify

		for (int i = lines.Count - 1; i >= 0; i--) {
			string line = lines [i];
			// if this is a method...
			bool critical_method = line.StartsWith ("SC-M: ");
			bool safe_method = line.StartsWith ("SSC-M: ");
			if (critical_method || safe_method) {
				// then check if the type is already marked
				int start = line.IndexOf (' ', 7) + 1;
				int length = line.IndexOf ("::", start) - start;
				string type = line.Substring (start, length);

				if (safe_method) {
					// if it's a safe method then it's not worth having it inside a SSC type
					if (lines.Contains ("SSC-T: " + type))
						lines.RemoveAt (i);
				}

				// if it's a critical (or safe) method then it's not worth having inside a SC type
				if (lines.Contains ("SC-T: " + type))
					lines.RemoveAt (i);
			}
		}

		lines.Sort ();
		return lines;
	}


	// 1. read all files for an assembly
	// 2. copy all lines that start with a '+'
	// 3. remove (from copy) all lines that start with a '-'
	// 4. add (to copy) all lines that start with a '!'
	// 5. sort
	// 6. save

	static void ProcessLine (string line)
	{
		if (String.IsNullOrEmpty (line))
			return;

		switch (line [0]) {
		case '#':
			// don't copy comments into the merged files
			break;
		case '!':
			always.Add (line.Substring (1));
			break;
		case '+':
			additions.Add (line.Substring (1));
			break;
		case '-':
			removals.Add (line.Substring (1));
			break;
		default:
			// if the line is not empty (spaces, tabs) then display an error
			if (line.Trim ().Length > 0)
				Console.WriteLine ("\t\tUnknown command '{0}'.", line [0]);
			break;
		}
	}

	static void ProcessFile (string file)
	{
		Console.Write ("\tfile '{0}' ", file);
		if (!File.Exists (file)) {
			Console.WriteLine ("not found");
			return;
		}

		using (StreamReader sr = new StreamReader (file)) {
			while (!sr.EndOfStream)
				ProcessLine (sr.ReadLine ());
		}
		Console.WriteLine ("processed");
	}

	static void Process (string assembly, string input, string output)
	{
		// read (add) all autogenerated [SecurityCritical] (from detect-sc)
		string filename = Path.Combine (Path.Combine (input, "automatic"), assembly + ".auto.sc");
		ProcessFile (filename);

		// read (add) all autogenerated [SecuritySafeCritical] (from detect-ssc)
		filename = Path.Combine (Path.Combine (input, "automatic"), assembly + ".auto.ssc");
		ProcessFile (filename);

		// read (add / remove) all manual overrides
		filename = Path.Combine (Path.Combine (input, "overrides"), assembly + ".manual");
		ProcessFile (filename);

		// read (add) all "required for compatibility" [SecurityCritical] (from find-sc)
		filename = Path.Combine (Path.Combine (input, "compatibility"), assembly + ".compat.sc");
		ProcessFile (filename);

		// make it easier for humans
		List<string> lines = Merge ();

		filename = Path.Combine (output, assembly + ".secattr");
		using (StreamWriter sw = new StreamWriter (filename)) {
			foreach (string line in lines)
				sw.WriteLine (line);
		}

		additions.Clear ();
		removals.Clear ();
		always.Clear ();
	}

	static int Main (string [] args)
	{
		if (args.Length < 3) {
			Console.WriteLine ("Usage: merge input-dir output-dir assembly.secattr");
			return 1;
		}

		string input = args [0];
		string output = args [1];
		string secattr = args [2];

		if (!secattr.EndsWith (".secattr")) {
			Console.WriteLine ("Usage: merge input-dir output-dir assembly.secattr");
			return 1;
		}

		string assembly = secattr.Substring (0, secattr.Length - ".secattr".Length);

		if (Array.IndexOf (PlatformCode.Assemblies, assembly) == -1) {
			Console.WriteLine ("asssembly {0} is not a platform code assembly", assembly);
			return 1;
		}

		// {input}/compatibility/"assembly".compat.sc	find-sc		only !
		// {input}/automatic/"assembly".auto.sc		detect-sc	only +
		// {input}/automatic/"assembly".auto.ssc	detect-ssc	only +
		// {input}/overrides/"assembly".manual		(manual)	+ or -
		//	into
		// {output}/"assembly".secattr

		Process (assembly, input, output);

		return 0;
	}
}
