/*
 * DrFx - FxCop Report Translator and Visualizer.
 * Copyright (C) 2010 Sasa Yuan
 * 
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */



using NUnit.Framework;
using Sasa.QualityTools.DrFx.Console;
using System;
using System.Collections.Generic;

namespace Sasa.QualityTools.DrFx.Console.Tests.Unit
{
    [TestFixture]
    public class GetoptTest
    {
        /// <summary>
        /// コマンドライン引数のショートオプションが期待通り解析できることをテストします。
        /// </summary>
        /// <param name="definition">ショートオプション定義文字列。</param>
        /// <param name="args">コマンドライン引数。</param>
        /// <returns>解析結果のオプション、および非オプション引数の文字列表現。</returns>
        [Test]
        [TestCaseSource(typeof(ParseShortOptionsTestCaseGenerator), "Generate")]
        public string TestParseShortOptions(string definition, string[] args)
        {
            List<string> results = new List<string>();

            Getopt getopt = new Getopt();
            getopt.ShortOption = definition;
            string[] nonopts = getopt.Parse(args, (opt, optarg) =>
            {
                results.Add(opt + "|" + optarg);
            });

            foreach (string nonopt in nonopts)
            {
                results.Add("@" + nonopt);
            }

            return String.Join(",", results.ToArray());
        }

        /// <summary>
        /// コマンドライン引数のロングオプションが期待通り解析できることをテストします。
        /// </summary>
        /// <param name="argument">コマンドライン引数。</param>
        /// <returns>解析結果のオプション、および非オプション引数の文字列表現。</returns>
        [Test]
        [TestCaseSource(typeof(ParseLongOptionsTestCaseGenerator), "Generate")]
        public string TestParseLongOptions(string argument)
        {
            List<string> results = new List<string>();

            Getopt getopt = new Getopt();
            getopt.AddLongOption("a", 'a', false);
            getopt.AddLongOption("b", 'b', true);
            string[] nonopts = getopt.Parse(argument.Split(' '), (opt, optarg) =>
            {
                results.Add(opt + "|" + optarg);
            });

            foreach (string nonopt in nonopts)
            {
                results.Add("@" + nonopt);
            }

            return String.Join(",", results.ToArray());
        }

        /// <summary>
        /// ショートオプション定義が正しく解析されることをテストします。
        /// </summary>
        [Test]
        [Description("ショートオプション定義が正しく解析されることをテストします。")]
        public void TestParseShortOptionDefinition()
        {
            Option[] options = Getopt.ParseShortOptionDefinition("ab:cCd:D:");
            List<Option> actual = new List<Option>(options);
            Assert.AreEqual(new Option('a', false), actual[0]);
            Assert.AreEqual(new Option('b', true), actual[1]);
            Assert.AreEqual(new Option('c', false), actual[2]);
            Assert.AreEqual(new Option('C', false), actual[3]);
            Assert.AreEqual(new Option('d', true), actual[4]);
            Assert.AreEqual(new Option('D', true), actual[5]);
        }

        /// <summary>
        /// シンボル指定によるオプション定義の検索が期待通り動作することをテストします。
        /// </summary>
        /// <param name="symbol">オプションを表すシンボル。</param>
        /// <returns>オプション定義の文字列表現。</returns>
        [TestCase('a', Result = "a|False",
            Description = "指定したシンボルに対応するオプション定義が取得できることをテストします。")]
        [TestCase('b', Result = "b|True",
            Description = "指定したシンボルに対応するオプション定義が取得できることをテストします。")]
        public string TestGetOption(char symbol)
        {
            Option[] options = new Option[]
            {
                new Option('a', false),
                new Option('b', true)
            };
            Option option = Getopt.FindOption(options, symbol);
            return option.Symbol + "|" + option.HasArgument;
        }

        /// <summary>
        /// 登録されていないシンボルでオプション定義を検索した場合は、オプション未定義例外が発生することをテストします。
        /// </summary>
        [Test]
        public void TestGetUndefinedOption()
        {
            Option[] options = new Option[]
            {
                new Option('a', false),
                new Option('b', true)
            };
            try
            {
                Option option = Getopt.FindOption(options, 'c');
                Assert.Fail();
            }
            catch (UndefinedOptionException e)
            {
                Assert.AreEqual("-c", e.SpecifiedOption);
            }
        }

        /// <summary>
        /// ショートオプション解析テスト用のテストケースデータ生成クラスです。
        /// </summary>
        private class ParseShortOptionsTestCaseGenerator
        {
            /// <summary>
            /// テストケースデータのコレクションを生成します。
            /// </summary>
            /// <returns>生成したテストケースデータのコレクション。</returns>
            private IEnumerable<TestCaseData> Generate()
            {
                yield return new TestCaseData("a", "aaa".Split(' '))
                    .Returns("@aaa")
                    .SetDescription("'-' がついていない引数は通常の引数と認識されることをテストします。");

                yield return new TestCaseData("a", "-a".Split(' '))
                    .Returns("a|")
                    .SetDescription("オプション定義で指定したシンボルがオプションと認識されることをテストします。");

                yield return new TestCaseData("a", "-aaa".Split(' '))
                    .Returns("a|,a|,a|")
                    .SetDescription("複合オプション内の全シンボルがオプションと認識されることをテストします。");

                yield return new TestCaseData("a", "-a -a -a".Split(' '))
                    .Returns("a|,a|,a|")
                    .SetDescription("オプションを連続指定しても全てオプションと認識されることをテストします。");

                yield return new TestCaseData("a", "-a -aa".Split(' '))
                    .Returns("a|,a|,a|")
                    .SetDescription("単独オプションと複合オプションが混在していても全てがオプションと認識されることをテストします。");

                yield return new TestCaseData("a:", "-a XXX".Split(' '))
                    .Returns("a|XXX")
                    .SetDescription("引数ありオプションのオプションと引数が別々の要素になっていても正しく認識されることをテストします。");

                yield return new TestCaseData("a:", "-aXXX".Split(' '))
                    .Returns("a|XXX")
                    .SetDescription("引数ありオプションのオプションと引数が同一の要素になっていても正しく認識されることをテストします。");

                yield return new TestCaseData("a", "-b".Split(' '))
                    .Returns("?|-b")
                    .SetDescription("オプション定義に含まれないオプションを指定するとシンボルが '?' になることをテストします。");

                yield return new TestCaseData("a", "-a aaa".Split(' '))
                    .Returns("a|,@aaa")
                    .SetDescription("引数なしオプションの直後の引数は通常の引数と認識されることをテストします。");

                yield return new TestCaseData("ab", "-a -b".Split(' '))
                    .Returns("a|,b|")
                    .SetDescription("複数のオプションが認識されることをテストします。");

                yield return new TestCaseData("ab", "-ab".Split(' '))
                    .Returns("a|,b|")
                    .SetDescription("複数の種類のオプションによる複合オプションが正しく認識されることをテストします。");

                yield return new TestCaseData("ab:", "-abXXX".Split(' '))
                    .Returns("a|,b|XXX")
                    .SetDescription("複合オプションとオプションの引数が同一の要素になっていても正しく認識されることをテストします。");

                yield return new TestCaseData("a", "-- -a".Split(' '))
                    .Returns("@-a")
                    .SetDescription("-- の後の要素はオプションとみなされないことをテストします。");

                yield return new TestCaseData("ab", "-- -a -b".Split(' '))
                    .Returns("@-a,@-b")
                    .SetDescription("-- の後の全ての要素はオプションとみなされないことをテストします。");

                yield return new TestCaseData("ab", "-a -- -b".Split(' '))
                    .Returns("a|,@-b")
                    .SetDescription("-- の前の要素はオプションとみなされ、後の要素はオプションとみなされないことをテストします。");

                yield return new TestCaseData("", "-- --".Split(' '))
                    .Returns("@--")
                    .SetDescription("-- の後の -- は通常の引数とみなされることをテストします。");

                yield return new TestCaseData("a:", "-a".Split(' '))
                    .Throws(typeof(MissingOptionArgumentException))
                    .SetDescription("引数ありオプションに引数が指定されていない場合に例外が発生することをテストします。");
            }
        }

        /// <summary>
        /// ロングオプション解析テスト用のテストケースデータ生成クラスです。
        /// </summary>
        private class ParseLongOptionsTestCaseGenerator
        {
            /// <summary>
            /// テストケースデータのコレクションを生成します。
            /// </summary>
            /// <returns>生成したテストケースデータのコレクション。</returns>
            private IEnumerable<TestCaseData> Generate()
            {
                yield return new TestCaseData("AAA")
                    .Returns("@AAA")
                    .SetDescription("'--' がついていない引数は通常の引数と認識されることをテストします。");

                yield return new TestCaseData("--a")
                    .Returns("a|")
                    .SetDescription("引数なしロングオプションが正しく認識されることをテストします。");

                yield return new TestCaseData("--a AAA")
                    .Returns("a|,@AAA")
                    .SetDescription("引数なしロングオプションと通常の引数が正しく認識されることをテストします。");

                yield return new TestCaseData("--b XXX")
                    .Returns("b|XXX")
                    .SetDescription("引数ありロングオプションとオプションの引数が正しく認識されることをテストします。");

                yield return new TestCaseData("--c")
                    .Returns("?|--c")
                    .SetDescription("登録されていないロングオプションを指定した場合は '?' と認識されることをテストします。");

                yield return new TestCaseData("--b")
                    .Throws(typeof(MissingOptionArgumentException))
                    .SetDescription("引数ありオプションに引数が指定されていない場合に例外が発生することをテストします。");
            }
        }
    }
}
