﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Oni.Akira;
using Oni.Collections;
using Oni.Metadata;
using Oni.Sound;
using Oni.Xml;

namespace Oni
{
    internal class Program
    {
        private static readonly InstanceFileManager fileManager = new InstanceFileManager();

        private static int Main(string[] args)
        {
            if (args.Length == 0)
            {
                Help(args);
                Console.WriteLine("Press any key to continue");
                Console.ReadKey();
                return 0;
            }

            if (args[0] == "-cdump")
            {
                InstanceMetadata.DumpCStructs(Console.Out);
                return 0;
            }

            Dae.IO.DaeReader.CommandLineArgs = args;

            if (args[0] == "-silent")
            {
                Console.SetOut(new StreamWriter(Stream.Null));
                Console.SetError(new StreamWriter(Stream.Null));

                var newArgs = new string[args.Length - 1];
                Array.Copy(args, 1, newArgs, 0, newArgs.Length);
                args = newArgs;
            }

            if (args[0] == "-noexcept")
            {
                var newArgs = new string[args.Length - 1];
                Array.Copy(args, 1, newArgs, 0, newArgs.Length);
                args = newArgs;
                return Execute(args);
            }

            args = AddSearchPaths(args);

            try
            {
                return Execute(args);
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.ToString());
                return 1;
            }
        }

        private static int Execute(string[] args)
        {
            if (args[0].StartsWith("-export:", StringComparison.Ordinal))
                return Unpack(args);

            switch (args[0])
            {
                case "-help":
                    return Help(args);

                case "-version":
                    return PrintVersion();

                case "-export":
                    return Unpack(args);

                case "pack":
                case "-import":
                case "-import:nosep":
                case "-import:sep":
                case "-import:ppc":
                case "-import:pc":
                    return Pack(args);

                case "-copy":
                    return Copy(args);

                case "-move":
                case "-move:overwrite":
                case "-move:delete":
                    return Move(args);

                case "-list":
                    return List(args);

                case "-deps":
                    return GetDependencies(args);

                case "extract":
                case "-extract:xml":
                    return ExportXml(args);

                case "-extract:tga":
                case "-extract:dds":
                case "-extract:png":
                case "-extract:jpg":
                case "-extract:bmp":
                case "-extract:tif":
                    return ExportTextures(args);

                case "-extract:wav":
                case "-extract:aif":
                case "-extract:pcm":
                    return ExportSounds(args);

                case "-extract:obj":
                case "-extract:dae":
                    return ExportGeometry(args);

                case "-extract:txt":
                    return ExportSubtitles(args);

                case "-create:akev":
                    return CreateAkira(args);

                case "-create:tram":
                case "-create:trbs":
                case "-create:txmp":
                case "-create:m3gm":
                case "-create:subt":
                case "-create:oban":
                case "-create":
                case "create":
                    return CreateGeneric(args);

                case "-grid:create":
                    return CreateGrids(args);

                case "-create:level":
                    return ImportLevel(args);

                case "-room:extract":
                    return ExtractRooms(args);

                case "film2xml":
                    return ConvertFilm2Xml(args);

                default:
                    Console.Error.WriteLine("Unknown command {0}", args[0]);
                    return 1;
            }
        }

        private static string[] AddSearchPaths(string[] args)
        {
            List<string> newArgs = new List<string>();

            for (int i = 0; i < args.Length; i++)
            {
                if (args[i] == "-search")
                {
                    i++;

                    if (i < args.Length)
                        fileManager.AddSearchPath(args[i]);
                }
                else
                {
                    newArgs.Add(args[i]);
                }
            }

            return newArgs.ToArray();
        }

        private static int Help(string[] args)
        {
            if (args.Length > 1 && args[1] == "enums")
            {
                HelpEnums();
                return 0;
            }

            Console.WriteLine("{0} [options] datfile", Environment.GetCommandLineArgs()[0]);
            Console.WriteLine();
            Console.WriteLine("Options:");
            Console.WriteLine("\t-export <directory>\t\tExport a Oni .dat file to directory");
            Console.WriteLine("\t-import <directory>\t\tImport a Oni .dat file from directory");
            Console.WriteLine("\t\t\t\t\tTarget file format is determined from source files (when possible)");
            Console.WriteLine("\t-import:sep <directory>\t\tImport a Oni .dat file from directory");
            Console.WriteLine("\t\t\t\t\tCreate a .dat file that uses .raw and .sep binary files (Mac and PC Demo)");
            Console.WriteLine("\t-import:nosep <directory>\tImport a Oni .dat file from directory");
            Console.WriteLine("\t\t\t\t\tCreate a .dat file that uses only .raw binary file (PC)");
            Console.WriteLine();
            Console.WriteLine("\t-extract:dds <directory>\tExtracts all textures (TXMP) from a Oni .dat/.oni file in DDS format");
            Console.WriteLine("\t-extract:tga <directory>\tExtracts all textures (TXMP) from a Oni .dat/.oni file in TGA format");
            Console.WriteLine("\t-extract:png <directory>\tExtracts all textures (TXMP) from a Oni .dat/.oni file in PNG format");
            Console.WriteLine("\t-extract:wav <directory>\tExtracts all sounds (SNDD) from a Oni .dat/.oni file in WAV format");
            Console.WriteLine("\t-extract:aif <directory>\tExtracts all sounds (SNDD) from a Oni .dat/.oni file in AIF format");
            Console.WriteLine("\t-extract:txt <directory>\tExtracts all subtitles (SUBT) from a Oni .dat/.oni file in TXT format");
            Console.WriteLine("\t-extract:obj <directory>\tExtracts all M3GM and ONCC instances to Wavefront OBJ files");
            Console.WriteLine("\t-extract:dae <directory>\tExtracts all M3GM and ONCC instances to Collada files");
            Console.WriteLine("\t-extract:xml <directory>\tExtracts all instances to XML files");
            Console.WriteLine();
            Console.WriteLine("\t-create:txmp <directory> [-nomipmaps] [-nouwrap] [-novwrap] [-format:bgr|rgba|bgr555|bgra5551|bgra4444|dxt1] [-envmap:texture_name] [-large] image_file");
            Console.WriteLine("\t-create:m3gm <directory> [-tex:texture_name] obj_file");
            Console.WriteLine("\t-create:trbs <directory> dae_file");
            Console.WriteLine("\t-create:subt <directory> txt_file");
            Console.WriteLine("\t-create <directory> xml_file\tCreates an .oni file from an XML file");
            Console.WriteLine();
            Console.WriteLine("\t-grid:create -out:<directory> rooms_src.dae level_geometry1.dae level_geometry2.dae ...\tGenerates pathfinding grids");
            Console.WriteLine();
            Console.WriteLine("\t-list\t\t\t\tLists the named instances contained in datfile");
            Console.WriteLine("\t-copy <directory>\t\tCopy an exported .oni file and its dependencies to directory");
            Console.WriteLine("\t-move <directory>\t\tMove an exported .oni file and its dependencies to directory");
            Console.WriteLine("\t-move:overwrite <directory>\tMove an exported .oni file and its dependencies to directory");
            Console.WriteLine("\t\t\t\t\tOverwrites any existing files");
            Console.WriteLine("\t-move:delete <directory>\tMove an exported .oni file and its dependencies to directory");
            Console.WriteLine("\t\t\t\t\tDeletes files at source when they already exist at destination");
            Console.WriteLine("\t-deps\t\t\t\tGet a list of exported .oni files the specified files depends on");
            Console.WriteLine("\t-version\t\t\tShow OniSplit versions");
            Console.WriteLine("\t-help\t\t\t\tShow this help");
            Console.WriteLine("\t-help enums\t\t\tShow a list of enums and flags used in XML files");
            Console.WriteLine();

            return 0;
        }

        private static void HelpEnums()
        {
            WriteEnums(typeof(InstanceMetadata));
            WriteEnums(typeof(ObjectMetadata));

            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine("Particles enums");
            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine();

            Utils.WriteEnum(typeof(Particles.ParticleFlags1));
            Utils.WriteEnum(typeof(Particles.ParticleFlags2));
            Utils.WriteEnum(typeof(Particles.EmitterFlags));
            Utils.WriteEnum(typeof(Particles.EmitterOrientation));
            Utils.WriteEnum(typeof(Particles.EmitterPosition));
            Utils.WriteEnum(typeof(Particles.EmitterRate));
            Utils.WriteEnum(typeof(Particles.EmitterSpeed));
            Utils.WriteEnum(typeof(Particles.EmitterDirection));
            Utils.WriteEnum(typeof(Particles.DisableDetailLevel));
            Utils.WriteEnum(typeof(Particles.AttractorSelector));
            Utils.WriteEnum(typeof(Particles.AttractorTarget));
            Utils.WriteEnum(typeof(Particles.EventType));
            Utils.WriteEnum(typeof(Particles.SpriteType));
            Utils.WriteEnum(typeof(Particles.StorageType));
            Utils.WriteEnum(typeof(Particles.ValueType));

            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine("Object enums");
            Console.WriteLine("-----------------------------------------------------");
            Console.WriteLine();

            Utils.WriteEnum(typeof(Physics.ObjectSetupFlags));
            Utils.WriteEnum(typeof(Physics.ObjectPhysicsType));
            Utils.WriteEnum(typeof(Physics.ObjectAnimationFlags));
        }

        private static void WriteEnums(Type type)
        {
#if !NETCORE
            foreach (var enumType in type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic))
            {
                if (!enumType.IsEnum)
                    continue;

                Utils.WriteEnum(enumType);
            }
#endif
        }

        private static int Unpack(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var prefixes = new List<string>();
            string outputDirPath = null;
            string sourceFilePath = null;

            foreach (string arg in args)
            {
                if (arg.StartsWith("-export", StringComparison.Ordinal))
                {
                    string prefix = null;
                    int i = arg.IndexOf(':');

                    if (i != -1)
                        prefix = arg.Substring(i + 1);

                    if (!string.IsNullOrEmpty(prefix))
                        prefixes.Add(prefix);
                }
                else if (outputDirPath == null)
                {
                    outputDirPath = Path.GetFullPath(arg);
                }
                else if (sourceFilePath == null)
                {
                    sourceFilePath = Path.GetFullPath(arg);
                }
            }

            var unpacker = new DatUnpacker(fileManager, outputDirPath);

            if (prefixes.Count > 0)
                unpacker.NameFilter = Utils.WildcardToRegex(prefixes);

            unpacker.ExportFiles(new[] { sourceFilePath });

            return 0;
        }

        private static int ExportTextures(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            int i = args[0].IndexOf(':');
            string fileType = null;

            if (i != -1)
                fileType = args[0].Substring(i + 1);

            var outputDirPath = Path.GetFullPath(args[1]);
            var sourceFilePaths = GetFileList(args, 2);

            var exporter = new Oni.Motoko.TextureExporter(fileManager, outputDirPath, fileType);
            exporter.ExportFiles(sourceFilePaths);

            return 0;
        }

        private static int ExportSounds(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            int i = args[0].IndexOf(':');
            string fileType = null;

            if (i != -1)
                fileType = args[0].Substring(i + 1);

            var outputDirPath = Path.GetFullPath(args[1]);
            var sourceFilePaths = GetFileList(args, 2);

            SoundExporter exporter;
            var noDemo = args.Any(a => a == "-nodemo");

            switch (fileType)
            {
                case "aif":
                    exporter = new AifExporter(fileManager, outputDirPath, noDemo);
                    break;
                case "wav":
                    exporter = new WavExporter(fileManager, outputDirPath, false, noDemo);
                    break;
                case "pcm":
                    exporter = new WavExporter(fileManager, outputDirPath, true, noDemo);
                    break;
                default:
                    throw new NotSupportedException(string.Format("Unsupported file type {0}", fileType));
            }

            exporter.ExportFiles(sourceFilePaths);

            return 0;
        }

        private static int ExportGeometry(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            int i = args[0].IndexOf(':');
            string fileType = null;

            if (i != -1)
                fileType = args[0].Substring(i + 1);

            var outputDirPath = Path.GetFullPath(args[1]);
            var sourceFilePaths = GetFileList(args, 2);

            var exporter = new DaeExporter(args, fileManager, outputDirPath, fileType);
            exporter.ExportFiles(sourceFilePaths);

            return 0;
        }

        private static int ExportSubtitles(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var sourceFilePaths = GetFileList(args, 2);

            var exporter = new SubtitleExporter(fileManager, outputDirPath);
            exporter.ExportFiles(sourceFilePaths);

            return 0;
        }

        private static int ExportXml(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var sourceFilePaths = GetFileList(args, 2);

            var exporter = new XmlExporter(fileManager, outputDirPath)
            {
                Recursive = args.Any(a => a == "-recurse"),
                MergeAnimations = args.Any(a => a == "-anim-merge"),
                NoAnimation = args.Any(a => a == "-noanim")
            };

            var animBodyFilePath = args.FirstOrDefault(a => a.StartsWith("-anim-body:", StringComparison.Ordinal));

            if (animBodyFilePath != null)
            {
                animBodyFilePath = Path.GetFullPath(animBodyFilePath.Substring("-anim-body:".Length));
                var file = fileManager.OpenFile(animBodyFilePath);
                exporter.AnimationBody = Totoro.BodyDatReader.Read(file.Descriptors[0]);
            }

            exporter.ExportFiles(sourceFilePaths);

            return 0;
        }

        private static int Pack(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var packer = new DatPacker();
            var inputPaths = new List<string>();

            if (args[0] == "pack")
            {
                for (int i = 1; i < args.Length; i++)
                {
                    var arg = args[i];

                    if (arg == "-out")
                    {
                        i++;
                        packer.TargetFilePath = Path.GetFullPath(args[i]);
                        continue;
                    }

                    if (arg.StartsWith("-type:", StringComparison.Ordinal))
                    {
                        switch (arg.Substring(6))
                        {
                            case "nosep":
                            case "pc":
                                packer.TargetTemplateChecksum = InstanceFileHeader.OniPCTemplateChecksum;
                                break;

                            case "sep":
                            case "pcdemo":
                            case "macintel":
                                packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum;
                                break;

                            case "ppc":
                                packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum;
                                packer.TargetBigEndian = true;
                                break;

                            default:
                                throw new ArgumentException(string.Format("Unknown output type {0}", arg.Substring(6)));
                        }

                        continue;
                    }

                    if (Directory.Exists(arg))
                    {
                        arg = Path.GetFullPath(arg);
                        Console.WriteLine("Reading directory {0}", arg);
                        inputPaths.AddRange(Directory.GetFiles(arg, "*.oni", SearchOption.AllDirectories));
                    }
                    else
                    {
                        var dirPath = Path.GetDirectoryName(arg);
                        var fileSpec = Path.GetFileName(arg);

                        if (string.IsNullOrEmpty(dirPath))
                            dirPath = Directory.GetCurrentDirectory();
                        else
                            dirPath = Path.GetFullPath(dirPath);

                        if (Directory.Exists(dirPath))
                        {
                            foreach (string filePath in Directory.GetFiles(dirPath, fileSpec))
                            {
                                Console.WriteLine("Reading {0}", filePath);
                                inputPaths.Add(filePath);
                            }
                        }
                    }
                }

                packer.Pack(fileManager, inputPaths);
            }
            else
            {
                switch (args[0])
                {
                    case "-import:nosep":
                    case "-import:pc":
                        packer.TargetTemplateChecksum = InstanceFileHeader.OniPCTemplateChecksum;
                        break;

                    case "-import:sep":
                    case "-import:pcdemo":
                    case "-import:macintel":
                        packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum;
                        break;

                    case "-import:ppc":
                        packer.TargetTemplateChecksum = InstanceFileHeader.OniMacTemplateChecksum;
                        packer.TargetBigEndian = true;
                        break;
                }

                for (int i = 1; i < args.Length - 1; i++)
                    inputPaths.Add(Path.GetFullPath(args[i]));

                packer.TargetFilePath = Path.GetFullPath(args[args.Length - 1]);
                packer.Import(fileManager, inputPaths.ToArray());
            }

            return 0;
        }

        private static int Copy(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var inputFilePaths = GetFileList(args, 2);
            var copy = new InstanceFileOperations();

            copy.Copy(fileManager, inputFilePaths, outputDirPath);

            return 0;
        }

        private static int Move(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var inputFilePaths = GetFileList(args, 2);
            var copy = new InstanceFileOperations();

            if (args[0] == "-move:delete")
                copy.MoveDelete(fileManager, inputFilePaths, outputDirPath);
            else if (args[0] == "-move:overwrite")
                copy.MoveOverwrite(fileManager, inputFilePaths, outputDirPath);
            else
                copy.Move(fileManager, inputFilePaths, outputDirPath);

            return 0;
        }

        private static int List(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            string sourceFilePath = Path.GetFullPath(args[1]);

            var file = fileManager.OpenFile(sourceFilePath);

            foreach (var descriptor in file.GetNamedDescriptors())
                Console.WriteLine(descriptor.FullName);

            return 0;
        }

        private static int GetDependencies(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var inputFilePaths = GetFileList(args, 1);

            InstanceFileOperations copy = new InstanceFileOperations();
            copy.GetDependencies(fileManager, inputFilePaths);

            return 0;
        }

        private static int PrintVersion()
        {
            Console.WriteLine("OniSplit version {0}", Utils.Version);
            return 0;
        }

        private static int CreateGrids(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var filePaths = GetFileList(args, 1);

            var roomsScene = Dae.Reader.ReadFile(filePaths[0]);
            var geometryMesh = Akira.AkiraDaeReader.Read(filePaths.Skip(1));
            var gridBuilder = new Akira.RoomGridBuilder(roomsScene, geometryMesh);
            string outputDirPath = null;

            foreach (string arg in args)
            {
                if (arg.StartsWith("-out:", StringComparison.Ordinal))
                    outputDirPath = Path.GetFullPath(arg.Substring(5));
            }

            if (string.IsNullOrEmpty(outputDirPath))
            {
                Console.Error.WriteLine("Output path must be specified");
                return 1;
            }

            gridBuilder.Build();
            AkiraDaeWriter.WriteRooms(gridBuilder.Mesh, Path.GetFileNameWithoutExtension(filePaths[0]), outputDirPath);

            return 0;
        }

        private static int ExtractRooms(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            string outputFilePath = null;

            foreach (string arg in args)
            {
                if (arg.StartsWith("-out:", StringComparison.Ordinal))
                    outputFilePath = Path.GetFullPath(arg.Substring(5));
            }

            if (string.IsNullOrEmpty(outputFilePath))
            {
                Console.Error.WriteLine("Output file path must be specified");
                return 1;
            }

            var extractor = new Akira.RoomExtractor(GetFileList(args, 1), outputFilePath);
            extractor.Extract();
            return 0;
        }

        private static int CreateAkira(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var filePaths = GetFileList(args, 2);

            Directory.CreateDirectory(outputDirPath);

            var importedFiles = new Set<string>(StringComparer.OrdinalIgnoreCase);
            var taskQueue = new Queue<ImporterTask>();

            foreach (string filePath in filePaths)
                importedFiles.Add(filePath);

            var importer = new AkiraImporter(args);

            Console.WriteLine("Importing {0}", filePaths[0]);

            importer.Import(filePaths, outputDirPath);

            QueueTasks(importedFiles, taskQueue, importer);
            ExecuteTasks(args, outputDirPath, importedFiles, taskQueue);

            return 0;
        }

        private static int CreateGeneric(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var targetType = TemplateTag.NONE;
            int colonIndex = args[0].IndexOf(':');

            if (colonIndex != -1)
            {
                string tagName = args[0].Substring(colonIndex + 1);
                targetType = (TemplateTag)Enum.Parse(typeof(TemplateTag), tagName, true);
            }

            string outputDirPath = Path.GetFullPath(args[1]);
            var filePaths = GetFileList(args, 2);

            Directory.CreateDirectory(outputDirPath);

            var importedFiles = new Set<string>(StringComparer.OrdinalIgnoreCase);
            var importQueue = new Queue<ImporterTask>();

            foreach (string filePath in filePaths)
            {
                if (importedFiles.Add(filePath))
                    importQueue.Enqueue(new ImporterTask(filePath, targetType));
            }

            ExecuteTasks(args, outputDirPath, importedFiles, importQueue);

            return 0;
        }

        private static void ExecuteTasks(string[] args, string outputDirPath, Set<string> importedFiles, Queue<ImporterTask> taskQueue)
        {
            while (taskQueue.Count > 0)
            {
                var task = taskQueue.Dequeue();

                if (!File.Exists(task.FilePath))
                {
                    Console.Error.WriteLine("File {0} does not exist", task.FilePath);
                    continue;
                }

                var importer = CreateImporterFromFileName(args, task);

                if (importer == null)
                {
                    Console.Error.WriteLine("{0} files cannot be imported as {1}", Path.GetExtension(task.FilePath), task.Type);
                    continue;
                }

                Console.WriteLine("Importing {0}", task.FilePath);

                importer.Import(task.FilePath, outputDirPath);

                QueueTasks(importedFiles, taskQueue, importer);
            }
        }

        private static Importer CreateImporterFromFileName(string[] args, ImporterTask task)
        {
            Importer importer = null;

            switch (Path.GetExtension(task.FilePath).ToLowerInvariant())
            {
                case ".bin":
                    importer = new BinImporter();
                    break;

                case ".xml":
                    importer = new XmlImporter(args);
                    break;

                case ".tga":
                case ".dds":
                case ".png":
                case ".jpg":
                case ".bmp":
                case ".tif":
                    if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.TXMP)
                        importer = new Oni.Motoko.TextureImporter(args);
                    break;

                case ".obj":
                case ".dae":
                    if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.M3GM)
                        importer = new Motoko.GeometryImporter(args);
                    else if (task.Type == TemplateTag.AKEV)
                        importer = new AkiraImporter(args);
                    else if (task.Type == TemplateTag.TRBS)
                        importer = new Totoro.BodySetImporter(args);
                    else if (task.Type == TemplateTag.OBAN)
                        importer = new Physics.ObjectAnimationImporter(args);
                    break;

                case ".wav":
                    if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SNDD)
                        importer = new WavImporter(args.Any(a => a == "-demo"));
                    break;

                case ".aif":
                case ".aifc":
                case ".afc":
                    if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SNDD)
                        importer = new AifImporter();
                    break;

                case ".txt":
                    if (task.Type == TemplateTag.NONE || task.Type == TemplateTag.SUBT)
                        importer = new SubtitleImporter();
                    break;
            }

            return importer;
        }

        private static void QueueTasks(Set<string> imported, Queue<ImporterTask> importQueue, Importer importer)
        {
            foreach (ImporterTask child in importer.Dependencies)
            {
                if (!imported.Contains(child.FilePath))
                {
                    imported.Add(child.FilePath);
                    importQueue.Enqueue(child);
                }
            }
        }

        private static int ConvertFilm2Xml(string[] args)
        {
            if (args.Length < 3)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var outputDirPath = Path.GetFullPath(args[1]);
            var inputFilePaths = GetFileList(args, 2);

            Directory.CreateDirectory(outputDirPath);

            foreach (string filePath in inputFilePaths)
                FilmToXmlConverter.Convert(filePath, outputDirPath);

            return 0;
        }

        private static int ImportLevel(string[] args)
        {
            if (args.Length < 2)
            {
                Console.Error.WriteLine("Invalid command line.");
                return 1;
            }

            var levelImporter = new Level.LevelImporter
            {
                Debug = args.Any(a => a == "-debug")
            };

            var outputDirPath = Path.GetFullPath(args[1]);

            if (string.IsNullOrEmpty(outputDirPath))
            {
                Console.Error.WriteLine("Output path must be specified");
                return 1;
            }

            var inputFiles = GetFileList(args, 2);

            if (inputFiles.Count == 0)
            {
                Console.Error.WriteLine("No input files specified");
                return 1;
            }

            if (inputFiles.Count > 1)
            {
                Console.Error.WriteLine("Too many input files specified, only one level can be created at a time");
                return 1;
            }

            levelImporter.Import(inputFiles[0], outputDirPath);
            return 0;
        }

        private static List<string> GetFileList(string[] args, int startIndex)
        {
            var fileSet = new Set<string>(StringComparer.OrdinalIgnoreCase);
            var fileList = new List<string>();

            foreach (var arg in args.Skip(startIndex))
            {
                if (arg[0] == '-')
                    continue;

                string dirPath = Path.GetDirectoryName(arg);
                string fileSpec = Path.GetFileName(arg);

                if (string.IsNullOrEmpty(dirPath))
                    dirPath = Directory.GetCurrentDirectory();
                else
                    dirPath = Path.GetFullPath(dirPath);

                if (Directory.Exists(dirPath))
                {
                    foreach (string filePath in Directory.GetFiles(dirPath, fileSpec))
                    {
                        if (fileSet.Add(filePath))
                            fileList.Add(filePath);
                    }
                }
            }

            if (fileList.Count == 0)
                throw new ArgumentException("No input files found");

            return fileList;
        }
    }
}
