/Config.cs
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace bytex64.WebThing {
    public struct SearchHandlerPair {
        public string Shortcut;
        public string Plugin;

        public SearchHandlerPair(string s, string p) {
            Shortcut = s;
            Plugin = p;
        }
    }

    public class Config {
        public static List<string> ConfigPath;
        public static string ConfigPathOut = null;
        public static List<string> PluginPath;

        public static Dictionary<string,string> Options;
        public static Dictionary<string,Dictionary<string,string>> PluginOptions;
        public static string[] Arguments;
        public static List<string> Plugins;
        public static List<SearchHandlerPair> SearchHandlers;
        public static string DefaultSearchHandler;

        private static HashSet<string> CommandLineOptions;
        private static Regex item_re = new Regex(@"^(?:([\w.]+)\.)?(\w+)$");
        private static Regex file_var_re = new Regex(@"^([\w.]+)\s*=\s*(.*)$");
        private static Regex file_command_re = new Regex(@"^([\w]+)\s*(.*)$");

        static Config() {
            Options = new Dictionary<string,string>();
            PluginOptions = new Dictionary<string,Dictionary<string,string>>();
            CommandLineOptions = new HashSet<string>();
            Plugins = new List<string>();
            SearchHandlers = new List<SearchHandlerPair>();

            // Set up ConfigPath
            IDictionary Env = Environment.GetEnvironmentVariables();
            ConfigPath = new List<string>();
            ConfigPath.Add("/etc/WebThing");
            if (Env.Contains("HOME")) {
                string homepath = Env["HOME"] + "/.config/WebThing";
                ConfigPath.Add(homepath);
                if (Directory.Exists(homepath))
                    ConfigPathOut = homepath;
            }
            string LocalAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            if (LocalAppData != "") {
                string homepath = LocalAppData + "/WebThing";
                ConfigPath.Add(homepath);
                if (Directory.Exists(homepath))
                    ConfigPathOut = homepath;
            }

            // Set up PluginPath
            PluginPath = new List<string>();
            PluginPath.Add(Environment.GetEnvironmentVariable("WEBTHING_HOME") + "/plugins");
            PluginPath.Add(Environment.GetEnvironmentVariable("WEBTHING_HOME") + "/searchplugins");
        }

        private static void ParseOption(string key, string val) {
            Match m = item_re.Match(key);
            string plugin = m.Groups[1].Value;
            string name = m.Groups[2].Value;
            if (plugin.Length > 0) {   // Plugin Option
                if (!PluginOptions.ContainsKey(plugin))
                    PluginOptions[plugin] = new Dictionary<string,string>();
                PluginOptions[plugin][name] = val;
            } else {                   // Global Option
                switch(key.ToLower()) {
                case "plugin":
                    Plugins.Add(val);
                    break;
                default:
                    Options[key] = val;
                    break;
                }
            }
        }

        public static void DumpOptions() {
            Console.WriteLine("Options:");
            foreach (string key in Options.Keys)
                Console.WriteLine("   {0}\t{1}", key, Options[key]);

            Console.WriteLine("Plugin Options:");
            foreach (string plugin in PluginOptions.Keys) {
                Console.WriteLine("    {0}", plugin);
                foreach (string key in PluginOptions[plugin].Keys)
                    Console.WriteLine("        {0}\t{1}", key, PluginOptions[plugin][key]);
            }

            Console.WriteLine("Arguments: {0}", String.Join(" ", Arguments));
        }

        public static void ParseCommandLine() {
            Queue<string> q = new Queue<string>();
            foreach (string s in System.Environment.GetCommandLineArgs()) {
                q.Enqueue(s);
            }
            q.Dequeue();   // Remove self argument

            Regex SimpleOpt = new Regex(@"^-([\w.]+)(?:[:=](.*))?$");
            Regex LongOpt   = new Regex(@"^--([\w.]+)$");
            List<string> args = new List<string>();

            while (q.Count > 0) {
                string opt = q.Dequeue();
                Match m;
                   
                m = SimpleOpt.Match(opt);
                if (m.Success) {
                    string key = m.Groups[1].Value;
                    string val = m.Groups[2].Value;
                    if (val.Length > 0) {
                        ParseOption(key, val);
                    } else {
                        ParseOption(key, null);
                    }
                    CommandLineOptions.Add(key);
                    continue;
                }

                m = LongOpt.Match(opt);
                if (m.Success) {
                    string key = m.Groups[1].Value;
                    if (q.Count > 0) {
                        string val = q.Peek();
                        if (!(SimpleOpt.IsMatch(val) || LongOpt.IsMatch(val))) {
                            q.Dequeue();
                            ParseOption(key, val);
                        } else {
                            ParseOption(key, null);
                        }
                    } else {
                        ParseOption(key, null);
                    }
                    CommandLineOptions.Add(key);
                    continue;
                }

                // else

                args.Add(opt);
            }
            Arguments = args.ToArray();
        }

        public static void Load() {
            foreach (string path in ConfigPath) {
                if (!Directory.Exists(path)) continue;
                string[] ConfigFiles = Directory.GetFiles(path, "*.conf");
                foreach (string file in ConfigFiles) {
                    LoadFile(file);
                }
            }
            if (Options.ContainsKey("config"))
                LoadFile(Options["config"]);
        }

        public static void LoadFile(string filename) {
            TextReader f;
            try {
                f = new StreamReader(filename);
            } catch (FileNotFoundException) {
                Console.WriteLine("Could not open configuration file {0}", filename);
                return;
            }

            string line;
            while ((line = f.ReadLine()) != null) {
                Match m;

                line = line.Trim();
                m = file_var_re.Match(line);
                if (m.Success) {
                    string key = m.Groups[1].Value;
                    if (!CommandLineOptions.Contains(key))
                        ParseOption(key, m.Groups[2].Value);
                    continue;
                }

                m = file_command_re.Match(line);
                if (m.Success) {
                    string cmd = m.Groups[1].Value.ToLower();
                    switch(cmd) {
                    case "plugin":
                        Plugins.Add(m.Groups[2].Value);
                        break;
                    case "searchhandler":
                        string[] args = Regex.Split(m.Groups[2].Value, @"\s+");
                        if (args.Length != 2) {
                            Console.WriteLine("Expecting two arguments for SearchHandler.");
                            break;
                        }
                        SearchHandlers.Add(new SearchHandlerPair(args[0], args[1]));
                        break;
                    case "defaultsearchhandler":
                        DefaultSearchHandler = m.Groups[2].Value;
                        break;
                    default:
                        Console.WriteLine("Unknown Command in {0}: {1}", filename, line);
                        break;
                    }
                    continue;
                }
            }

            f.Close();
        }

        public static void SaveFile(string filename) {
            TextWriter f;
            try {
                f = new StreamWriter(filename);
            } catch (IOException) {
                Console.WriteLine("Could not save configuration to {0}", filename);
                return;
            }

            foreach (string key in Options.Keys) {
                if (CommandLineOptions.Contains(key)) continue;
                f.WriteLine("{0} = {1}", key, Options[key]);
            }

            foreach (string plugin in PluginOptions.Keys) {
                foreach (string key in PluginOptions[plugin].Keys) {
                    string pkey = String.Format("{0}.{1}", plugin, key);
                    if (CommandLineOptions.Contains(pkey)) continue;
                    f.WriteLine("{0} = {1}", pkey, PluginOptions[plugin][key]);
                }
            }

            f.Close();
        }

        public static void SaveFile(WebThingPlugin p, string filename) {
            string plugin_name = p.GetType().FullName;
            SaveFile(plugin_name, filename);
        }

        public static void SaveFile(string plugin_name, string filename) {
            // TODO: Throw exception if plugin does not exist
            TextWriter f = new StreamWriter(filename);

            foreach (string key in PluginOptions[plugin_name].Keys) {
                f.WriteLine("{0}.{1} = {2}", plugin_name, key, PluginOptions[plugin_name][key]);
            }

            f.Close();
        }

        // Data parsers
        public static bool ParseBool(string v) {
            switch(v.ToLower()) {
            case "true":
            case "t":
            case "1":
            case "on":
            case "yes":
                return true;
            }
            return false;
        }
    }
}