// modified version of Mono.CSharp.Driver

// driver.cs: The compiler command line driver.
//
// Authors:
//   Miguel de Icaza (miguel@gnu.org)
//   Marek Safar (marek.safar@gmail.com)
//
// Dual licensed under the terms of the MIT X11 or GNU GPL
//
// Copyright 2001, 2002, 2003 Ximian, Inc (http://www.ximian.com)
// Copyright 2004, 2005, 2006, 2007, 2008 Novell, Inc
// Copyright 2011 Xamarin Inc
//

#if NET_4_6 && !NET_STANDARD_2_0
#define QC_SUPPORTED
#endif

#if QC_SUPPORTED
using Mono.CSharp;
using System;
using System.IO;
using System.Reflection.Emit;

namespace CSharpCompiler
{
    /// <summary>
    ///    The compiler driver.
    /// </summary>
    public class CustomDynamicDriver
    {
        readonly CompilerContext ctx;

        public CustomDynamicDriver(CompilerContext ctx)
        {
            this.ctx = ctx;
        }

        public Report Report
        {
            get
            {
                return ctx.Report;
            }
        }

        void tokenize_file(SourceFile sourceFile, ModuleContainer module, ParserSession session)
        {
            Stream input;

            try
            {
                input = sourceFile.GetDataStream();
            }
            catch
            {
                Report.Error(2001, "Source file `" + sourceFile.Name + "' could not be found");
                return;
            }

            using (input)
            {
                SeekableStreamReader reader = new SeekableStreamReader(input, ctx.Settings.Encoding);
                var file = new CompilationSourceFile(module, sourceFile);

                Tokenizer lexer = new Tokenizer(reader, file, session, ctx.Report);
                int token, tokens = 0, errors = 0;

                while ((token = lexer.token()) != Token.EOF)
                {
                    tokens++;
                    if (token == Token.ERROR)
                        errors++;
                }
                Console.WriteLine("Tokenized: " + tokens + " found " + errors + " errors");
            }

            return;
        }

        public void Parse(ModuleContainer module)
        {
            bool tokenize_only = module.Compiler.Settings.TokenizeOnly;
            var sources = module.Compiler.SourceFiles;

            Location.Initialize(sources);

            var session = new ParserSession
            {
                UseJayGlobalArrays = true,
                LocatedTokens = new LocatedToken[15000]
            };

            for (int i = 0; i < sources.Count; ++i)
            {
                if (tokenize_only)
                {
                    tokenize_file(sources[i], module, session);
                }
                else
                {
                    Parse(sources[i], module, session, Report);
                }
            }
        }


        public void Parse(SourceFile file, ModuleContainer module, ParserSession session, Report report)
        {
            Stream input;

            try
            {
                input = file.GetDataStream();
            }
            catch
            {
                report.Error(2001, "Source file `{0}' could not be found", file.Name);
                return;
            }

            // Check 'MZ' header
            if (input.ReadByte() == 77 && input.ReadByte() == 90)
            {

                report.Error(2015, "Source file `{0}' is a binary file and not a text file", file.Name);
                input.Close();
                return;
            }

            input.Position = 0;
            SeekableStreamReader reader = new SeekableStreamReader(input, ctx.Settings.Encoding, session.StreamReaderBuffer);

            Parse(reader, file, module, session, report);

            if (ctx.Settings.GenerateDebugInfo && report.Errors == 0 && !file.HasChecksum)
            {
                input.Position = 0;
                var checksum = session.GetChecksumAlgorithm();
                file.SetChecksum(checksum.ComputeHash(input));
            }

            reader.Dispose();
            input.Close();
        }

        public static void Parse(SeekableStreamReader reader, SourceFile sourceFile, ModuleContainer module, ParserSession session, Report report)
        {
            var file = new CompilationSourceFile(module, sourceFile);
            module.AddTypeContainer(file);

            CSharpParser parser = new CSharpParser(reader, file, report, session);
            parser.parse();
        }



        //
        // Main compilation method
        //
        public bool Compile(out AssemblyBuilder outAssembly, AppDomain domain, bool generateInMemory)
        {
            var settings = ctx.Settings;

            outAssembly = null;
            //
            // If we are an exe, require a source file for the entry point or
            // if there is nothing to put in the assembly, and we are not a library
            //
            if (settings.FirstSourceFile == null &&
                ((settings.Target == Target.Exe || settings.Target == Target.WinExe || settings.Target == Target.Module) ||
                settings.Resources == null))
            {
                Report.Error(2008, "No files to compile were specified");
                return false;
            }

            if (settings.Platform == Platform.AnyCPU32Preferred && (settings.Target == Target.Library || settings.Target == Target.Module))
            {
                Report.Error(4023, "Platform option `anycpu32bitpreferred' is valid only for executables");
                return false;
            }

            TimeReporter tr = new TimeReporter(settings.Timestamps);
            ctx.TimeReporter = tr;
            tr.StartTotal();

            var module = new ModuleContainer(ctx);
            RootContext.ToplevelTypes = module;

            tr.Start(TimeReporter.TimerType.ParseTotal);
            Parse(module);
            tr.Stop(TimeReporter.TimerType.ParseTotal);

            if (Report.Errors > 0)
                return false;

            if (settings.TokenizeOnly || settings.ParseOnly)
            {
                tr.StopTotal();
                tr.ShowStats();
                return true;
            }

            var output_file = settings.OutputFile;
            string output_file_name;
            /* if (output_file == null)
             {
                 var source_file = settings.FirstSourceFile;

                 if (source_file == null)
                 {
                     Report.Error(1562, "If no source files are specified you must specify the output file with -out:");
                     return false;
                 }

                 output_file_name = source_file.Name;
                 int pos = output_file_name.LastIndexOf('.');

                 if (pos > 0)
                     output_file_name = output_file_name.Substring(0, pos);

                 output_file_name += settings.TargetExt;
                 output_file = output_file_name;
             }
             else
             {*/
            output_file_name = Path.GetFileName(output_file);

            /*  if (string.IsNullOrEmpty(Path.GetFileNameWithoutExtension(output_file_name)) ||
                  output_file_name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0)
              {
                  Report.Error(2021, "Output file name is not valid");
                  return false;
              }
          }*/


            var assembly = new AssemblyDefinitionDynamic(module, output_file_name, output_file);
            module.SetDeclaringAssembly(assembly);

            var importer = new ReflectionImporter(module, ctx.BuiltinTypes);
            assembly.Importer = importer;

            var loader = new DynamicLoader(importer, ctx);
            loader.LoadReferences(module);

            if (!ctx.BuiltinTypes.CheckDefinitions(module))
                return false;

            if (!assembly.Create(domain, AssemblyBuilderAccess.RunAndSave))
                return false;

            module.CreateContainer();

            loader.LoadModules(assembly, module.GlobalRootNamespace);

            module.InitializePredefinedTypes();

            if (settings.GetResourceStrings != null)
                module.LoadGetResourceStrings(settings.GetResourceStrings);

            tr.Start(TimeReporter.TimerType.ModuleDefinitionTotal);
            module.Define();
            tr.Stop(TimeReporter.TimerType.ModuleDefinitionTotal);

            if (Report.Errors > 0)
                return false;

            if (settings.DocumentationFile != null)
            {
                var doc = new DocumentationBuilder(module);
                doc.OutputDocComment(output_file, settings.DocumentationFile);
            }

            assembly.Resolve();

            if (Report.Errors > 0)
                return false;


            tr.Start(TimeReporter.TimerType.EmitTotal);
            assembly.Emit();
            tr.Stop(TimeReporter.TimerType.EmitTotal);

            if (Report.Errors > 0)
            {
                return false;
            }

            tr.Start(TimeReporter.TimerType.CloseTypes);
            module.CloseContainer();
            tr.Stop(TimeReporter.TimerType.CloseTypes);

            tr.Start(TimeReporter.TimerType.Resouces);
            if (!settings.WriteMetadataOnly)
                assembly.EmbedResources();
            tr.Stop(TimeReporter.TimerType.Resouces);

            if (Report.Errors > 0)
                return false;


            if (!generateInMemory) assembly.Save();
            outAssembly = assembly.Builder;


            tr.StopTotal();
            tr.ShowStats();

            return Report.Errors == 0;
        }
    }

}
#endif
