//
// Parameters.cs: Class that contains information about command line parameters
//
// Author:
//   Marek Sieradzki (marek.sieradzki@gmail.com)
//
// (C) 2005 Marek Sieradzki
//
// 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.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Microsoft.Build.BuildEngine;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace Mono.XBuild.CommandLine {
	public class Parameters {
	
		string			consoleLoggerParameters;
		bool			displayHelp;
		IList			flatArguments;
		IList			loggers;
		LoggerVerbosity		loggerVerbosity;
		bool			noConsoleLogger;
		bool			noLogo;
		string			projectFile;
		BuildPropertyGroup	properties;
		IList			remainingArguments;
		Hashtable		responseFiles;
		string[]		targets;
		bool			validate;
		string			validationSchema;
		string			toolsVersion;
		
		string			responseFile;
	
		public Parameters ()
		{
			consoleLoggerParameters = "";
			displayHelp = false;
			loggers = new ArrayList ();
			loggerVerbosity = LoggerVerbosity.Normal;
			noConsoleLogger = false;
			noLogo = false;
			properties = new BuildPropertyGroup ();
			targets = new string [0];
			
			responseFile = Path.Combine (
					Path.GetDirectoryName (Assembly.GetExecutingAssembly ().Location),
					"xbuild.rsp");
		}
		
		public void ParseArguments (string[] args)
		{
			bool autoResponse = true;
			flatArguments = new ArrayList ();
			remainingArguments = new ArrayList ();
			responseFiles = new Hashtable ();
			FileLoggerParameters = new string[10];
			foreach (string s in args) {
				if (s.StartsWith ("/noautoresponse") || s.StartsWith ("/noautorsp")) {
					autoResponse = false;
					continue;
				}
				if (s [0] != '@') {
					flatArguments.Add (s);
					continue;
				}
				string responseFilename = Path.GetFullPath (UnquoteIfNeeded (s.Substring (1)));
				if (responseFiles.ContainsKey (responseFilename))
					ReportError (1, String.Format ("We already have {0} file.", responseFilename));
				responseFiles [responseFilename] = responseFilename;
				LoadResponseFile (responseFilename);
			}
			if (autoResponse == true) {
				// FIXME: we do not allow nested auto response file
				LoadResponseFile (responseFile);
			}
			foreach (string s in flatArguments) {
				if (s [0] != '/' || !ParseFlatArgument (s))
					remainingArguments.Add (s);
			}
			if (remainingArguments.Count == 0) {
				string[] sln_files = Directory.GetFiles (Directory.GetCurrentDirectory (), "*.sln");
				string[] proj_files = Directory.GetFiles (Directory.GetCurrentDirectory (), "*proj");

				if (sln_files.Length == 0 && proj_files.Length == 0)
					ReportError (3, "Please specify the project or solution file " +
							"to build, as none was found in the current directory.");

				if (sln_files.Length == 1 && proj_files.Length > 0) {
					var projects_table = new Dictionary<string, string> ();
					foreach (string pfile in SolutionParser.GetAllProjectFileNames (sln_files [0])) {
						string full_path = Path.GetFullPath (pfile);
						projects_table [full_path] = full_path;
					}

					if (!proj_files.Any (p => !projects_table.ContainsKey (Path.GetFullPath (p))))
						// if all the project files in the cur dir, are referenced
						// from the single .sln in the cur dir, then pick the sln
						proj_files = new string [0];
				}

				if (sln_files.Length + proj_files.Length > 1)
					ReportError (5, "Please specify the project or solution file " +
							"to build, as more than one solution or project file was found " +
							"in the current directory");

				if (sln_files.Length == 1)
					projectFile = sln_files [0];
				else
					projectFile = proj_files [0];
			} else if (remainingArguments.Count == 1) {
				projectFile = (string) remainingArguments [0];
			} else {
				ReportError (4, "Too many project files specified");
			}
		}

		private string UnquoteIfNeeded(string arg)
		{
			if (arg.StartsWith("\""))
				return arg.Substring(1, arg.Length - 2);
			return arg;
		}

		void LoadResponseFile (string filename)
		{
			StreamReader sr = null;
			string line;
			try {
				sr = new StreamReader (filename);
                                StringBuilder sb = new StringBuilder ();

                                while ((line = sr.ReadLine ()) != null) {
                                        int t = line.Length;

                                        for (int i = 0; i < t; i++) {
                                                char c = line [i];

						if (c == '#')
							// comment, ignore rest of the line
							break;

                                                if (c == '"' || c == '\'') {
                                                        char end = c;

                                                        for (i++; i < t; i++) {
                                                                c = line [i];

                                                                if (c == end)
                                                                        break;
                                                                sb.Append (c);
                                                        }
                                                } else if (c == ' ') {
                                                        if (sb.Length > 0) {
                                                                flatArguments.Add (sb.ToString ());
                                                                sb.Length = 0;
                                                        }
                                                } else
                                                        sb.Append (c);
                                        }
                                        if (sb.Length > 0){
                                                flatArguments.Add (sb.ToString ());
                                                sb.Length = 0;
                                        }
                                }
                        } catch (IOException x) {
				ErrorUtilities.ReportWarning (2, String.Format (
							"Error loading response file. (Exception: {0}). Ignoring.",
							x.Message));
			} finally {
                                if (sr != null)
                                        sr.Close ();
                        }
		}
		
		private bool ParseFlatArgument (string s)
		{
			switch (s) {
			case "/help":
			case "/h":
			case "/?":
				ErrorUtilities.ShowUsage ();
				break;
			case "/nologo":
				noLogo = true;
				break;
			case "/version":
			case "/ver":
				ErrorUtilities.ShowVersion (true);
				break;
			case "/noconsolelogger":
			case "/noconlog":
				noConsoleLogger = true;
				break;
			case "/validate":
			case "/val":
				validate = true;
				break;
			case "/fl":
			case "/filelogger":
				if (FileLoggerParameters [0] == null)
					FileLoggerParameters [0] = String.Empty;
				break;
			default:
				if (s.StartsWith ("/fl") && s.Length == 4 && Char.IsDigit (s[3])) {
					int index = Int32.Parse (s[3].ToString ());
					if (FileLoggerParameters [index] == null)
						FileLoggerParameters [index] = String.Empty;
				} else if (s.StartsWith ("/fileloggerparameters") || s.StartsWith ("/flp")) {
					ProcessFileLoggerParameters (s);
				} else if (s.StartsWith ("/target:") || s.StartsWith ("/t:")) {
					ProcessTarget (s);
				} else if (s.StartsWith ("/property:") || s.StartsWith ("/p:")) {
					if (!ProcessProperty (s))
						return false;
				} else  if (s.StartsWith ("/logger:") || s.StartsWith ("/l:")) {
					ProcessLogger (s);
				} else if (s.StartsWith ("/verbosity:") || s.StartsWith ("/v:")) {
					ProcessVerbosity (s);
				} else if (s.StartsWith ("/consoleloggerparameters:") || s.StartsWith ("/clp:")) {
					ProcessConsoleLoggerParameters (s);
				} else if (s.StartsWith ("/validate:") || s.StartsWith ("/val:")) {
					ProcessValidate (s);
				} else if (s.StartsWith ("/toolsversion:") || s.StartsWith ("/tv:")) {
					ToolsVersion = s.Split (':') [1];
				} else
					return false;
				break;
			}

			return true;
		}
		
		internal void ProcessTarget (string s)
		{
			TryProcessMultiOption (s, "Target names must be specified as /t:Target1;Target2",
						out targets);
		}
		
		internal bool ProcessProperty (string s)
		{
			string[] splitProperties;
			if (!TryProcessMultiOption (s, "Property name and value expected as /p:<prop name>=<prop value>",
						out splitProperties))
				return false;

			foreach (string st in splitProperties) {
				if (st.IndexOf ('=') < 0) {
					ReportError (5,
							"Invalid syntax. Property name and value expected as " +
							"<prop name>=[<prop value>]");
					return false;
				}
				string [] property = st.Split ('=');
				properties.SetProperty (property [0], property.Length == 2 ? property [1] : "");
			}

			return true;
		}

		bool TryProcessMultiOption (string s, string error_message, out string[] values)
		{
			values = null;
			int colon = s.IndexOf (':');
			if (colon + 1 == s.Length) {
				ReportError (5, error_message);
				return false;
			}

			values = s.Substring (colon + 1).Split (';');
			return true;
		}

		private void ReportError (int errorCode, string message)
		{
			throw new CommandLineException (message, errorCode);
		}

		private void ReportError (int errorCode, string message, Exception cause)
		{
			throw new CommandLineException (message, cause, errorCode);
		}

		internal void ProcessLogger (string s)
		{
			loggers.Add (new LoggerInfo (s));
		}
		
		internal void ProcessVerbosity (string s)
		{
			string[] temp = s.Split (':');
			switch (temp [1]) {
			case "q":
			case "quiet":
				loggerVerbosity = LoggerVerbosity.Quiet;
				break;
			case "m":
			case "minimal":
				loggerVerbosity = LoggerVerbosity.Minimal;
				break;
			case "n":
			case "normal":
				loggerVerbosity = LoggerVerbosity.Normal;
				break;
			case "d":
			case "detailed":
				loggerVerbosity = LoggerVerbosity.Detailed;
				break;
			case "diag":
			case "diagnostic":
				loggerVerbosity = LoggerVerbosity.Diagnostic;
				break;
			}
		}

		void ProcessFileLoggerParameters (string s)
		{
			int colon = s.IndexOf (':');
			if (colon + 1 == s.Length)
				ReportError (5, "Invalid syntax, specify parameters as /fileloggerparameters[n]:parameters");

			int index = 0;
			string key = s.Substring (0, colon);
			if (Char.IsDigit (key [key.Length - 1]))
			//if (key.Length == 22 && Char.IsDigit (key [21]))
				index = Int32.Parse (key [key.Length - 1].ToString ());

			FileLoggerParameters [index] = s.Substring (colon + 1);
		}

		internal void ProcessConsoleLoggerParameters (string s)
		{
			int colon = s.IndexOf (':');
			if (colon + 1 == s.Length)
				ReportError (5, "Invalid syntax, specify parameters as /clp:parameters");

			consoleLoggerParameters = s.Substring (colon + 1);
		}
		
		internal void ProcessValidate (string s)
		{
			string[] temp;
			validate = true;
			temp = s.Split (':');
			validationSchema = temp [1];
		}
		public bool DisplayHelp {
			get { return displayHelp; }
		}
		
		public bool NoLogo {
			get { return noLogo; }
		}
		
		public string ProjectFile {
			get { return projectFile; }
		}
		
		public string[] Targets {
			get { return targets; }
		}
		
		public BuildPropertyGroup Properties {
			get { return properties; }
		}
		
		public IList Loggers {
			get { return loggers; }
		}
		
		public LoggerVerbosity LoggerVerbosity {
			get { return loggerVerbosity; }
		}
		
		public string ConsoleLoggerParameters {
			get { return consoleLoggerParameters; }
		}
		
		public bool NoConsoleLogger {
			get { return noConsoleLogger; }
		}

		public string[] FileLoggerParameters { get; set; }
		
		public bool Validate {
			get { return validate; }
		}
		
		public string ValidationSchema {
			get { return validationSchema; }
		}

		public string ToolsVersion {
			get { return toolsVersion; }
			private set { toolsVersion = value; }
		}
		
	}
}

