﻿using System;
using System.Collections.Generic;
using System.IO;

namespace Oni.Metadata
{
    internal class DumpVisitor : MetaTypeVisitor
    {
        #region Private data
        private readonly InstanceMetadata metadata;
        private readonly TextWriter writer;
        private Stack<ActiveField> activeFields;
        private MetaType topLevelType;
        private MetaType currentType;
        private int fieldSize;
        private int position;
        private string indent = "";
        private Field field;
        #endregion

        #region private class ActiveField

        private class ActiveField
        {
            public Field Field;
            public int Index;

            public ActiveField(Field field)
            {
                this.Field = field;
                this.Index = -1;
            }
        }

        #endregion

        public DumpVisitor(TextWriter writer, InstanceMetadata metadata)
        {
            this.writer = writer;
            this.metadata = metadata;
        }

        public MetaType TopLevelType => topLevelType;
        public MetaType Type => currentType;

        public Field Field
        {
            get
            {
                if (activeFields.Count == 0)
                    return null;

                return activeFields.Peek().Field;
            }
        }

        public int Position => position;

        public string GetCurrentFieldName()
        {
            if (activeFields.Count == 0)
                return null;

            List<string> names = new List<string>();

            foreach (ActiveField state in activeFields)
            {
                if (string.IsNullOrEmpty(state.Field.Name))
                    return null;

                if (state.Index >= 0)
                    names.Add(string.Format("{0}[{1}]", state.Field.Name, state.Index));
                else
                    names.Add(state.Field.Name);
            }

            names.Add(topLevelType.Name);
            names.Reverse();

            return string.Join(".", names.ToArray());
        }

        public string GetParentFieldName()
        {
            if (activeFields.Count == 0)
                return null;

            var names = new List<string>();

            foreach (ActiveField state in activeFields)
            {
                if (string.IsNullOrEmpty(state.Field.Name))
                    return null;

                if (state.Index >= 0)
                    names.Add(string.Format("{0}[{1}]", state.Field.Name, state.Index));
                else
                    names.Add(state.Field.Name);
            }

            names.Add(topLevelType.Name);
            names.Reverse();
            names.RemoveAt(names.Count - 1);

            return string.Join(".", names.ToArray());
        }

        public override void VisitByte(MetaByte type)
        {
            writer.Write(indent);
            writer.Write("uint8_t");
        }

        public override void VisitInt16(MetaInt16 type)
        {
            writer.Write(indent);
            writer.Write("int16_t");
        }

        public override void VisitUInt16(MetaUInt16 type)
        {
            writer.Write(indent);
            writer.Write("uint16_t");
        }

        public override void VisitInt32(MetaInt32 type)
        {
            writer.Write(indent);
            writer.Write("int32_t");
        }

        public override void VisitUInt32(MetaUInt32 type)
        {
            writer.Write(indent);
            writer.Write("uint32_t");
        }

        public override void VisitInt64(MetaInt64 type)
        {
            writer.Write(indent);
            writer.Write("int64_t");
        }

        public override void VisitUInt64(MetaUInt64 type)
        {
            writer.Write(indent);
            writer.Write("uint64_t");
        }

        public override void VisitFloat(MetaFloat type)
        {
            writer.Write(indent);
            writer.Write("float");
        }

        public override void VisitColor(MetaColor type)
        {
            writer.Write(indent);
            writer.Write("uint32_t");
        }

        public override void VisitRawOffset(MetaRawOffset type)
        {
            writer.Write(indent);
            writer.Write("void*");
        }

        public override void VisitSepOffset(MetaSepOffset type)
        {
            writer.Write(indent);
            writer.Write("void*");
        }

        public override void VisitPointer(MetaPointer type)
        {
            writer.Write(indent);
            writer.Write("{0}*", metadata.GetTemplate(type.Tag).Type.Name);
        }

        public override void VisitBoundingBox(MetaBoundingBox type)
        {
            writer.Write(indent);
            writer.Write("bbox");
        }

        public override void VisitBoundingSphere(MetaBoundingSphere type)
        {
            writer.Write(indent);
            writer.Write("bsphere");
        }

        public override void VisitMatrix4x3(MetaMatrix4x3 type)
        {
            writer.Write(indent);
            writer.Write("matrix43");
        }

        public override void VisitPlane(MetaPlane type)
        {
            writer.Write(indent);
            writer.Write("plane");
        }

        public override void VisitQuaternion(MetaQuaternion type)
        {
            writer.Write(indent);
            writer.Write("quat");
        }

        public override void VisitVector2(MetaVector2 type)
        {
            writer.Write(indent);
            writer.Write("vec2");
        }

        public override void VisitVector3(MetaVector3 type)
        {
            writer.Write(indent);
            writer.Write("vec3");
        }

        public override void VisitString(MetaString type)
        {
            writer.Write(indent);
            writer.Write("const char*");
        }

        public override void VisitPadding(MetaPadding type)
        {
            writer.Write(indent);
            writer.Write("std::array<{0}>", type.Size);
        }

        public override void VisitStruct(MetaStruct type)
        {
            if (topLevelType == null)
                topLevelType = type;

            writer.WriteLine();
            writer.WriteLine("struct {0} {{", type.Name);
            indent += "\t";

            foreach (Field field in type.Fields)
            {
                field.Type.Accept(this);
                writer.WriteLine(" {0};", field.Name);
            }

            indent = indent.Substring(0, indent.Length - 1);
            writer.WriteLine("};");
        }

        public override void VisitArray(MetaArray type)
        {
            writer.Write(indent);
        }

        public override void VisitVarArray(MetaVarArray type)
        {
        }
    }
}
