﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using Oni.Imaging;
using Oni.Metadata;
using Oni.Xml;

namespace Oni.Motoko
{
    internal class TextureXmlImporter
    {
        private readonly XmlImporter importer;
        private readonly XmlReader xml;
        private readonly string filePath;

        public TextureXmlImporter(XmlImporter importer, XmlReader xml, string filePath)
        {
            this.importer = importer;
            this.xml = xml;
            this.filePath = filePath;
        }

        public void Import()
        {
            xml.ReadStartElement();

            var name = Importer.DecodeFileName(Path.GetFileNameWithoutExtension(filePath));
            var flags = MetaEnum.Parse<InstanceMetadata.TXMPFlags>(xml.ReadElementContentAsString("Flags", ""));
            var format = MetaEnum.Parse<InstanceMetadata.TXMPFormat>(xml.ReadElementContentAsString("Format", ""));

            int width = 0;
            int height = 0;
            int speed = 1;

            if (xml.IsStartElement("Width"))
                width = xml.ReadElementContentAsInt();

            if (xml.IsStartElement("Height"))
                height = xml.ReadElementContentAsInt();

            string envMapName = null;

            if (xml.IsStartElement("EnvMap"))
            {
                envMapName = xml.ReadElementContentAsString();

                if (envMapName != null && envMapName.Length == 0)
                    envMapName = null;
            }

            if (xml.IsStartElement("Speed"))
                speed = xml.ReadElementContentAsInt();

            var imageFilePaths = new List<string>();
            var inputDirPath = Path.GetDirectoryName(filePath);

            while (xml.IsStartElement("Image"))
            {
                string imageFilePath = xml.ReadElementContentAsString();

                if (!Path.IsPathRooted(imageFilePath))
                    imageFilePath = Path.Combine(inputDirPath, imageFilePath);

                if (!File.Exists(imageFilePath))
                    throw new IOException(string.Format("Could not find image file '{0}'", imageFilePath));

                imageFilePaths.Add(imageFilePath);
            }

            xml.ReadEndElement();

            var surfaces = new List<Surface>();

            foreach (string imageFilePath in imageFilePaths)
                surfaces.Add(TgaReader.Read(imageFilePath));

            if (surfaces.Count == 0)
                throw new InvalidDataException("No images found. A texture must have at least one image.");

            int imageWidth = 0;
            int imageHeight = 0;

            foreach (Surface surface in surfaces)
            {
                if (imageWidth == 0)
                    imageWidth = surface.Width;
                else if (imageWidth != surface.Width)
                    throw new NotSupportedException("All animation frames must have the same size.");

                if (imageHeight == 0)
                    imageHeight = surface.Height;
                else if (imageHeight != surface.Height)
                    throw new NotSupportedException("All animation frames must have the same size.");
            }

            if (width == 0)
                width = imageWidth;
            else if (width > imageWidth)
                throw new NotSupportedException("Cannot upscale images.");

            if (height == 0)
                height = imageHeight;
            else if (height > imageHeight)
                throw new NotSupportedException("Cannot upscale images.");

            //if (envMapName != null && surfaces.Count > 1)
            //    throw new NotSupportedException("Animated textures cannot have an environment map.");

            if (width != imageWidth || height != imageHeight)
            {
                for (int i = 0; i < surfaces.Count; i++)
                    surfaces[i] = surfaces[i].Resize(width, height);
            }

            flags |= InstanceMetadata.TXMPFlags.SwapBytes;

            if (envMapName != null)
                flags |= InstanceMetadata.TXMPFlags.HasEnvMap;

            for (int i = 0; i < surfaces.Count; i++)
            {
                BinaryWriter writer;

                if (i == 0)
                    writer = importer.BeginXmlInstance(TemplateTag.TXMP, name, i.ToString(CultureInfo.InvariantCulture));
                else
                    writer = importer.BeginXmlInstance(TemplateTag.TXMP, null, i.ToString(CultureInfo.InvariantCulture));

                writer.Skip(128);
                writer.Write((int)flags);
                writer.WriteUInt16(width);
                writer.WriteUInt16(height);
                writer.Write((int)format);

                if (i == 0 && surfaces.Count > 1)
                    writer.WriteInstanceId(surfaces.Count);
                else
                    writer.Write(0);

                if (envMapName != null)
                    writer.WriteInstanceId(surfaces.Count + ((surfaces.Count > 1) ? 1 : 0));
                else
                    writer.Write(0);

                writer.Write(importer.RawWriter.Align32());
                writer.Skip(12);

                var mainSurface = surfaces[i];

                var levels = new List<Surface> { mainSurface };

                if ((flags & InstanceMetadata.TXMPFlags.HasMipMaps) != 0)
                {
                    int mipWidth = width;
                    int mipHeight = height;

                    while (mipWidth > 1 || mipHeight > 1)
                    {
                        mipWidth = Math.Max(mipWidth >> 1, 1);
                        mipHeight = Math.Max(mipHeight >> 1, 1);

                        levels.Add(mainSurface.Resize(mipWidth, mipHeight));
                    }
                }

                foreach (var level in levels)
                {
                    var surface = level.Convert(TextureFormatToSurfaceFormat(format));
                    importer.RawWriter.Write(surface.Data);
                }

                importer.EndXmlInstance();
            }

            if (surfaces.Count > 1)
            {
                var txan = importer.CreateInstance(TemplateTag.TXAN);

                using (var writer = txan.OpenWrite(12))
                {
                    writer.WriteInt16(speed);
                    writer.WriteInt16(speed);
                    writer.Write(0);
                    writer.Write(surfaces.Count);
                    writer.Write(0);

                    for (int i = 1; i < surfaces.Count; i++)
                        writer.WriteInstanceId(i);
                }
            }

            if (envMapName != null)
            {
                importer.CreateInstance(TemplateTag.TXMP, envMapName);
            }
        }

        private static SurfaceFormat TextureFormatToSurfaceFormat(InstanceMetadata.TXMPFormat format)
        {
            switch (format)
            {
                case InstanceMetadata.TXMPFormat.BGRA4444:
                    return SurfaceFormat.BGRA4444;

                case InstanceMetadata.TXMPFormat.BGR555:
                    return SurfaceFormat.BGRX5551;

                case InstanceMetadata.TXMPFormat.BGRA5551:
                    return SurfaceFormat.BGRA5551;

                case InstanceMetadata.TXMPFormat.RGBA:
                    return SurfaceFormat.RGBA;

                case InstanceMetadata.TXMPFormat.BGR:
                    return SurfaceFormat.BGRX;

                case InstanceMetadata.TXMPFormat.DXT1:
                    return SurfaceFormat.DXT1;

                default:
                    throw new NotSupportedException(string.Format("Texture format {0} is not supported", format));
            }
        }
    }
}
