protected virtual AtomSymbol[] GetBaseProperties(MoleculeSymbol molecule)
 {
     return molecule.Base
         .Select(x => x.Recurse(y => y.Base))
         .ValueOrDefault(Enumerable.Empty<ComplexValueSymbol>())
         .SelectMany(x => x.Properties)
         .ToArray();
 }
        protected virtual void WriteConstructor(MoleculeSymbol molecule)
        {
            Write("{0} {1}(", molecule.IsAbstract ? "protected" : "public", GetMoleculeClassName(molecule, molecule));

            var baseProperties = GetBaseProperties(molecule);
            var allProperties = molecule.Properties
                .Concat(baseProperties);

            Write(allProperties.Delimit(", ", x => string.Format("{0} {1}", GetPublicTypeString(x, molecule), Inflector.Camelize(x.Name))));
            WriteLine(")");

            if (baseProperties.Length > 0)
            {
                using (WithIndentation())
                    WriteLine(": base({0})", baseProperties.Delimit(", ", x => Inflector.Camelize(x.Name)));
            }

            using (WithBlock())
                WritePropertyAssignmentAndValidation(molecule.Properties);
        }
        protected virtual void WriteEquavalenceImplementation(MoleculeSymbol molecule)
        {
            string moleculeClassName = GetMoleculeClassName(molecule, molecule);

            if (!GenerateEquavalence)
                return;

            WriteLine();
            WriteLine("public bool Equals({0} other)", moleculeClassName);
            using (WithBlock())
            {
                WriteLine("if (ReferenceEquals(other, null)) return false;");
                WriteLine("if (GetType() != other.GetType()) return false;");
                WriteLine();

                foreach (var property in molecule.Properties)
                {
                    bool isOptional = property.Cardinality.IsZeroOrMore();
                    bool isMany = property.Cardinality.CanBeMoreThanOne();
                    bool isValueType = property.Type is ExternalType && ((ExternalType)property.Type).Type.IsValueType;

                    if (isOptional && !isMany)
                    {
                        WriteLine(isValueType
                                ? "if ({0}.HasValue != other.{0}.HasValue) return false;"
                                : "if (ReferenceEquals({0}, null) != ReferenceEquals(other.{0}, null)) return false;",
                            property.Name);
                    }

                    if (!isMany)
                    {
                        string prefix = "";
                        if (isOptional)
                        {
                            prefix = string.Format(isValueType
                                    ? "{0}.HasValue && "
                                    : "!ReferenceEquals({0}, null) && ",
                                property.Name);
                        }

                        WriteLine("if({0}!{1}.Equals(other.{1})) return false;", prefix, property.Name);
                    }
                    else
                        WriteLine("if(!{0}.SequenceEqual(other.{0})) return false;", property.Name);

                    WriteLine();
                }

                WriteLine(
                    molecule.Base
                        .Select(x => string.Format("return base.Equals(({0})other);", GetMoleculeClassName(x)))
                        .ValueOrDefault("return true;"));
            }

            WriteLine();

            WriteLine("public override bool Equals(object obj)");
            using (WithBlock())
            {
                WriteLine("var other = obj as {0};", moleculeClassName);
                WriteLine("return !ReferenceEquals(other, null) && Equals(other);");
            }

            WriteLine();

            WriteLine("public override int GetHashCode()");
            using (WithBlock())
            {
                WriteLine(
                    molecule.Base
                        .Select(x => string.Format("int hash = base.GetHashCode();"))
                        .ValueOrDefault("int hash = 1;"));

                foreach (var property in molecule.Properties)
                {
                    bool isOptional = property.Cardinality.IsZeroOrMore();
                    bool isMany = property.Cardinality.CanBeMoreThanOne();
                    bool isValueType = property.Type is ExternalType && ((ExternalType)property.Type).Type.IsValueType;

                    if (!isMany)
                    {
                        if (isOptional)
                        {
                            WriteLine(isValueType
                                          ? "hash = Hashing.Mix(hash + ({0}.HasValue ? {0}.GetHashCode() : 0));"
                                          : "hash = Hashing.Mix(hash + (!ReferenceEquals({0}, null) ? {0}.GetHashCode() : 0));",
                                      property.Name);
                        }
                        else
                            WriteLine("hash = Hashing.Mix(hash + {0}.GetHashCode());", property.Name);
                    }
                    else
                        WriteLine("hash = {0}.Aggregate(hash, (h, item) => Hashing.Mix(h + item.GetHashCode()));", property.Name);
                }

                WriteLine("return hash;");
            }

            WriteLine();

            WriteLine("public static bool operator ==({0} left, {0} right)", moleculeClassName);
            using (WithBlock())
            {
                WriteLine("if (ReferenceEquals(left, null) != ReferenceEquals(right, null)) return false;");
                WriteLine("return ReferenceEquals(left, null) || left.Equals(right);");
            }

            WriteLine();

            WriteLine("public static bool operator !=({0} left, {0} right)", moleculeClassName);
            using (WithBlock())
            {
                WriteLine("return !(left == right);");
            }
        }
 protected virtual string GetMoleculeClassName(MoleculeSymbol molecule, ISymbol relativeTo = null)
 {
     return GetRelativeName(molecule.FullName, relativeTo != null ? relativeTo.FullName : null);
 }
 protected virtual Maybe<string> GetBaseClassName(MoleculeSymbol molecule, ISymbol relativeTo = null)
 {
     return molecule.Base.Select(x => GetMoleculeClassName(x, relativeTo));
 }
        protected virtual void WritePropertyImplmementation(AtomSymbol property, MoleculeSymbol parent)
        {
            string privateType = GetPrivateTypeString(property, parent);
            string publicType = GetPublicTypeString(property, parent);
            string fieldName = Inflector.Camelize(property.Name);
            bool isMany = property.Cardinality.CanBeMoreThanOne();
            Maybe<string> description = GetPropertyDescription(property);

            WriteLine();
            WriteLine("private {0} _{1};", privateType, fieldName);
            if (description.HasValue)
            {
                WriteLine("/// <summary>");
                WriteLine("/// " + description.Value);
                WriteLine("/// </summary>");
            }
            WriteLine("public {0} {1} {{ get {{ return _{2}{3}; }} private set {{ _{2} = value{4}; }} }}", publicType, property.Name, fieldName, isMany ? ".AsEnumerable()" : "", isMany ? ".ToArray()" : "");
        }
        protected virtual void WriteMolecule(MoleculeSymbol molecule)
        {
            Write("public");
            if(molecule.IsAbstract) Write(" abstract");
            Write(" class {0}", GetMoleculeClassName(molecule, molecule));

            var baseClassName = GetBaseClassName(molecule, molecule);

            baseClassName.Run(x => Write(" : {0}", x));

            if (GenerateEquavalence)
            {
                Write(baseClassName.HasValue ? ", " : " : ");
                Write("IEquatable<{0}>", GetMoleculeClassName(molecule, molecule));
            }

            WriteLine();

            using (WithBlock())
            {
                WriteConstructor(molecule);
                WriteEssenceBuilderImplementation(molecule);
                WriteEquavalenceImplementation(molecule);

                foreach (var property in molecule.Properties)
                    WritePropertyImplmementation(property, molecule);
            }

            WriteLine();
        }
        protected virtual void WriteEssenceBuilderImplementation(MoleculeSymbol molecule)
        {
            if (!GenerateEssence)
                return;

            var baseValues = molecule.Base
                .Select(x => x.Recurse(y => y.Base))
                .Squash()
                .ToArray();

            var baseProperties = baseValues
                .SelectMany(x => x.Properties)
                .ToArray();

            var properties = molecule.Properties.Concat(baseProperties).ToArray();

            if (!molecule.Properties.Any() && molecule.IsAbstract)
                return;

            Func<MoleculeSymbol, bool> needsEssence = x => x.Properties.Any();

            var closestBase = baseValues.TryFirst(needsEssence);
            var furthestBase = baseValues.TryLast(needsEssence);

            WriteLine();
            WriteLine("public {0}{1}class Essence{2}",
                closestBase.HasValue ? "new " : "",
                molecule.IsAbstract ? "abstract " : "",
                closestBase.Select(x => string.Format(" : {0}.Essence", GetMoleculeClassName(x, molecule))).ValueOrDefault(""));

            using (WithBlock())
            {
                foreach (var property in molecule.Properties)
                {
                    string publicType = GetPublicTypeString(property, molecule);
                    WriteLine("public {0} {1} {{ protected get; set; }}", publicType, property.Name);
                }

                WriteLine();

                WriteLine("public {0}{1} Create()", closestBase.HasValue ? "new " : "", GetMoleculeClassName(molecule, molecule));
                using (WithBlock())
                {
                    WriteLine("return ({0})CreateValue();", GetMoleculeClassName(molecule, molecule));
                }

                WriteLine();

                if (!molecule.IsAbstract)
                {
                    WriteLine("protected {0} {1} CreateValue()", furthestBase.HasValue ? "override" : "virtual", furthestBase.Or(molecule).Select(x => GetMoleculeClassName(x, molecule).ToString()).Value);
                    using (WithBlock())
                    {
                        WriteLine("return new {0}({1});", GetMoleculeClassName(molecule, molecule), properties.Delimit(", ", x => x.Name));
                    }
                }
                else if(!furthestBase.HasValue)
                    WriteLine("protected abstract {0} CreateValue();", GetMoleculeClassName(molecule, molecule));
            }

            WriteLine();
            WriteLine("public {0}Essence ToEssence()", closestBase.HasValue ? "new " : "");
            using (WithBlock())
                WriteLine("return (Essence)CreateEssence();");

            WriteLine();

            if (!molecule.IsAbstract)
            {
                WriteLine("protected {0} {1}.Essence CreateEssence()", furthestBase.HasValue ? "override" : "virtual",
                          furthestBase.Or(molecule).Select(x => GetMoleculeClassName(x, molecule).ToString()).Value);
                using (WithBlock())
                {
                    WriteLine("return new Essence");
                    using (WithBlock(withStatementEnd: true))
                    {
                        WriteLine(properties.Delimit(",\r\n", x => string.Format("{0} = {0}", x.Name)));
                    }
                }
            }
            else if(!furthestBase.HasValue)
                WriteLine("protected abstract {0}.Essence CreateEssence();", GetMoleculeClassName(molecule, molecule));
        }