﻿using System;
using System.Collections.Generic;
using System.Text;
using Sopka.Tools.CodeWriters.Umbrello;
using Sopka.Tools.CodeWriters.XmiModels;

namespace Sopka.Tools.CodeWriters.Generators.Umbrello
{
    public sealed class EntityFwClassGenerator : IGenerator
    {
        private readonly CSharpWriter _writer;
        private readonly XmiModels.UmlClass _classDefinition;
        private readonly UmbrelloContext _context;

        public EntityFwClassGenerator(CSharpWriter writer, XmiModels.UmlClass classDefinition, UmbrelloContext context)
        {
            _writer = writer;
            _classDefinition = classDefinition;
            _context = context;
        }

        public void Dispose()
        {
            _writer.Dispose();
        }

        public void Initialize()
        {
            _writer.OpenOrCreate();
            _writer.WriteUsing("System");
            _writer.WriteNamespace(_context.GetNamespaceForNode(_classDefinition));
        }

        public void Run()
        {
            _writer.WriteUsing("System");
            _writer.WriteUsing("System.Collections.Generic");
            _writer.WriteUsing("System.ComponentModel.DataAnnotations");
            _writer.WriteUsing("System.ComponentModel.DataAnnotations.Schema");
            _writer.WriteEmptyLine();
            _writer.WriteSummary(_classDefinition.GetSummary(_context.SummaryLanguage));
            _writer.WriteAttribute("Table", new[] { CSharpWriter.MemberArgument.CreateValue(_classDefinition.GetDbSetName()) });
            _writer.WriteClass(CSharpWriter.ScopePublic, "partial", _classDefinition.Name, null, null);
            try
            {
                var useFluentPKDefinition = _classDefinition.IsComplexPrimaryKey();
                if (!useFluentPKDefinition)
                {
                    foreach (var pkNode in _classDefinition.GetPrimaryKeys())
                    {
                        if (pkNode is UmlAttribute)
                        {
                            _writeAttribute(_writer, pkNode as UmlAttribute, true);
                        }
                        else
                        {
                            _writeRelation(_writer, pkNode as UmlAssociationConnection, true);
                        }
                    }
                }                

                foreach (var node in _classDefinition.Attributes)
                {
                    if (!useFluentPKDefinition && node.GetPrimaryKeyIndex() != null) continue;
                    _writeAttribute(_writer, node, false);
                }

                foreach (var node in _classDefinition.Relations)
                {
                    if (!useFluentPKDefinition && node.GetPrimaryKeyIndex() != null) continue;
                    _writeRelation(_writer, node, false);
                }
            }
            finally
            {
                _writer.EndScope(); // end class
            }
        }

        private void _writeAttribute(CSharpWriter writer, UmlAttribute umlAttribute, bool asPrimaryKey)
        {
            writer.WriteEmptyLine();
            writer.WriteSummary(umlAttribute.GetSummary(_context.SummaryLanguage));
                
            if (asPrimaryKey)
            {
                writer.WriteAttribute("Key");
                writer.WriteAttribute("Column", new[] {
                    CSharpWriter.MemberArgument.CreateValue(umlAttribute.GetPrimaryKeyIndex(), true, "Order")
                });
            }
            if (umlAttribute.IsRequired())
            {
                writer.WriteAttribute("Required");
            }
            var maxLength = umlAttribute.GetMaxLength();
            if (maxLength != null)
            {
                writer.WriteAttribute("MaxLength", new[] {
                    CSharpWriter.MemberArgument.CreateValue(maxLength.Value)
                });
            }
            else if (umlAttribute.IsUnlimitedLength())
            {
                writer.WriteAttribute("MaxLength");
            }
            var dataType = umlAttribute.GetDataTypeName(true);
            writer.WriteProperty(CSharpWriter.ScopePublic, dataType, umlAttribute.Name);
        }

        private void _writeRelation(CSharpWriter writer, UmlAssociationConnection connection, bool asPrimaryKey)
        {
            writer.WriteEmptyLine();

            string summary = connection.GetSummary(_context.SummaryLanguage);
            if (summary != null)
                writer.WriteSummary(summary);
            else
                writer.WriteSummary(connection.Parent.GetSummary(_context.SummaryLanguage));
            var multiplicity = connection.GetOtherSide().Multiplicity;
            var isRequired = connection.GetOtherSide().IsRequired();
            var relatedEntity = connection.GetOtherSide().Type;
            //var relatedEntity = connection.GetOtherSide().GetRelatedEntityPrimaryKeys();
            switch (multiplicity)
            {
                case AssociationMultiplicity.One:
                    if (asPrimaryKey)
                    {
                        writer.WriteAttribute("Key");
                        writer.WriteAttribute("Column", new[] {
                            CSharpWriter.MemberArgument.CreateValue(connection.GetPrimaryKeyIndex(), true, "Order")
                        });
                    }
                    if (isRequired)
                    {
                        writer.WriteAttribute("Required");
                    }
                    writer.WriteProperty(CSharpWriter.ScopePublic, relatedEntity.GetMemberTypeName(connection, true), connection.Name);
                    writer.WriteAttribute("ForeignKey", new[] { CSharpWriter.MemberArgument.CreateValue(connection.Name) });
                    writer.WriteProperty(CSharpWriter.ScopePublic, relatedEntity.Name, connection.GetCommentValue("Name", relatedEntity.Name));
                    break;
                case AssociationMultiplicity.Many:
                    writer.Write("private ICollection<" + relatedEntity.Name + "> _" + connection.GetFieldName(relatedEntity) + ";");
                    //[InverseProperty("Author")]
                    writer.WriteAttribute("InverseProperty", new[] {
                        CSharpWriter.MemberArgument.CreateValue(connection.GetCommentValue("Name", connection.Type.Name))
                    });
                    writer.WriteProperty(CSharpWriter.ScopePublic, "ICollection<" + relatedEntity.Name + ">", connection.GetFieldName(relatedEntity), null,
                        (w) =>
                        {
                            w.WriteIf("_" + connection.GetFieldName(relatedEntity) + " == null", (w1) =>
                            {
                                w1.Write("_" + connection.GetFieldName(relatedEntity) + " = new List<" + relatedEntity.Name + ">();");
                            });
                            w.Write("return  _" + connection.GetFieldName(relatedEntity) + ";");
                        },
                        (w) =>
                        {
                            w.Write("_" + connection.GetFieldName(relatedEntity) + " = value;");
                        });
                    break;
            }
        }
    }
}
