/*
 * Program.cs
 *
 * This source file is part of the FoundationDB open source project
 *
 * Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;
using System.Text.RegularExpressions;

namespace coveragetool
{
    class CoverageCase
    {
        public string File;
        public int Line;
        public string Comment;
        public string Condition;
    };

    class Program
    {
        public static int Main(string[] args)
        {
            if (args.Length < 2)
            {
                Console.WriteLine("Usage:");
                Console.WriteLine("  coveragetool [coveragefile] [inputpath]*");
                return 100;
            }
            Console.WriteLine("coveragetool {0}", string.Join(" ", args));

            string output = args[0];
            string[] inputPaths = args.Skip(1).Where(p=>!p.Contains(".g.") && !p.Contains(".amalgamation.")).ToArray();

            var outputFile = new FileInfo( output );

            /*var allFiles = inputPaths.SelectMany( path =>
                new DirectoryInfo( Path.GetDirectoryName(path) )
                    .EnumerateFiles( Path.GetFileName(path), SearchOption.AllDirectories )
                ).ToArray();*/

            var exists = inputPaths.ToLookup(n=>n);

            CoverageCase[] cases = new CoverageCase[0];
            var outputTime = DateTime.MinValue;
            if (outputFile.Exists)
            {
                string[] oldArgs;
                ParseOutput(output, out cases, out oldArgs);
                if (oldArgs.Length == inputPaths.Length && !oldArgs.Zip(inputPaths,(a,b)=>a!=b).Any(b=>b))
                    outputTime = outputFile.LastWriteTimeUtc;
            }

            var changedFiles = inputPaths
                .Where( fi=>new FileInfo(fi).LastWriteTimeUtc > outputTime )
                .ToLookup(n=>n);

            cases = cases
                .Where(c => exists.Contains(c.File) && !changedFiles.Contains(c.File))
                .Concat( changedFiles.SelectMany( f => ParseSource( f.Key ) ) )
                .ToArray();

            Console.WriteLine("  {0}/{1} files scanned", changedFiles.Count, inputPaths.Length);
            Console.WriteLine("  {0} coverage cases found", cases.Length);

            WriteOutput(output, cases, inputPaths);

            return 0;
        }

        private static string ValueOrDefault(XAttribute attr, string def)
        {
            if (attr == null) return def;
            else return attr.Value;
        }

        public static void ParseOutput(string filename, out CoverageCase[] cases, out string[] args)
        {
            var doc = XDocument.Load(filename).Element("CoverageTool");
            cases =
                doc.Element("CoverageCases")
                    .Elements("Case")
                    .Select(c =>
                        new CoverageCase { File = c.Attribute("File").Value, Line = int.Parse(c.Attribute("Line").Value), Comment=c.Attribute("Comment").Value, Condition=ValueOrDefault(c.Attribute("Condition"),"") }
                        )
                    .ToArray();
            args =
                doc.Element("Inputs")
                    .Elements("Input")
                    .Select(i => i.Value)
                    .ToArray();
        }
        public static void WriteOutput(string filename, CoverageCase[] cases, string[] args)
        {
            var doc = new XDocument(
                new XElement("CoverageTool",
                    new XElement("CoverageCases",
                        cases.Select(c =>
                            new XElement("Case",
                                new XAttribute("File", c.File),
                                new XAttribute("Line", c.Line.ToString()),
                                new XAttribute("Comment", c.Comment),
                                new XAttribute("Condition", c.Condition)
                                )
                            )
                        ),
                    new XElement("Inputs",
                        args.Select(a => new XElement("Input", a)))
                ));
            doc.Save(filename);
        }
        public static CoverageCase[] ParseSource(string filename)
        {
            var regex = new Regex( @"^([^/]|/[^/])*(TEST|INJECT_FAULT|SHOULD_INJECT_FAULT)[ \t]*\(([^)]*)\)" );

            var lines = File.ReadAllLines(filename);
            return Enumerable.Range(0, lines.Length)
                .Where( i=>regex.IsMatch(lines[i]) && !lines[i].StartsWith("#define") )
                .Select( i=>new CoverageCase { 
                    File = filename, 
                    Line = i+1,
                    Comment = FindComment(lines[i]),
                    Condition = regex.Match(lines[i]).Groups[3].Value
                    } )
                .ToArray();
        }
        public static string FindComment(string line)
        {
            int comment = line.IndexOf("//");
            if (comment == -1)
                return "";
            else
                return line.Substring(comment + 2).Trim();
        }
    }
}
