using System;
using System.IO;
using System.Text;

using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Utilities.Encoders;

namespace Org.BouncyCastle.Asn1.Utilities
{
    public sealed class Asn1Dump
    {
        private static readonly string NewLine = Platform.NewLine;

        private Asn1Dump()
        {
        }

        private const string Tab = "    ";
        private const int SampleSize = 32;

        /**
         * dump a Der object as a formatted string with indentation
         *
         * @param obj the Asn1Object to be dumped out.
         */
        private static void AsString(string indent, bool verbose, Asn1Object obj, StringBuilder buf)
        {
            if (obj is Asn1Null)
            {
                buf.Append(indent);
                buf.Append("NULL");
                buf.Append(NewLine);
            }
            else if (obj is Asn1Sequence)
            {
                buf.Append(indent);
                if (obj is BerSequence)
                {
                    buf.Append("BER Sequence");
                }
                else if (!(obj is DLSequence))
                {
                    buf.Append("DER Sequence");
                }
                else
                {
                    buf.Append("Sequence");
                }
                buf.Append(NewLine);

                Asn1Sequence sequence = (Asn1Sequence)obj;
                string elementsIndent = indent + Tab;

                for (int i = 0, count = sequence.Count; i < count; ++i)
                {
                    AsString(elementsIndent, verbose, sequence[i].ToAsn1Object(), buf);
                }
            }
            else if (obj is Asn1Set)
            {
                buf.Append(indent);
                if (obj is BerSet)
                {
                    buf.Append("BER Set");
                }
                else if (!(obj is DLSet))
                {
                    buf.Append("DER Set");
                }
                else
                {
                    buf.Append("Set");
                }
                buf.Append(NewLine);

                Asn1Set set = (Asn1Set)obj;
                string elementsIndent = indent + Tab;

                for (int i = 0, count = set.Count; i < count; ++i)
                {
                    AsString(elementsIndent, verbose, set[i].ToAsn1Object(), buf);
                }
            }
            else if (obj is DerApplicationSpecific)
            {
                AsString(indent, verbose, ((DerApplicationSpecific)obj).TaggedObject, buf);
            }
            else if (obj is Asn1TaggedObject)
            {
                buf.Append(indent);
                if (obj is BerTaggedObject)
                {
                    buf.Append("BER Tagged ");
                }
                else if (!(obj is DLTaggedObject))
                {
                    buf.Append("DER Tagged ");
                }
                else
                {
                    buf.Append("Tagged ");
                }

                Asn1TaggedObject o = (Asn1TaggedObject)obj;

                buf.Append(Asn1Utilities.GetTagText(o));

                if (!o.IsExplicit())
                {
                    buf.Append(" IMPLICIT ");
                }

                buf.Append(NewLine);

                string baseIndent = indent + Tab;

                AsString(baseIndent, verbose, o.GetBaseObject().ToAsn1Object(), buf);
            }
            else if (obj is DerObjectIdentifier)
            {
                buf.Append(indent + "ObjectIdentifier(" + ((DerObjectIdentifier)obj).Id + ")" + NewLine);
            }
            else if (obj is Asn1RelativeOid)
            {
                buf.Append(indent + "RelativeOID(" + ((Asn1RelativeOid)obj).Id + ")" + NewLine);
            }
            else if (obj is DerBoolean)
            {
                buf.Append(indent + "Boolean(" + ((DerBoolean)obj).IsTrue + ")" + NewLine);
            }
            else if (obj is DerInteger)
            {
                buf.Append(indent + "Integer(" + ((DerInteger)obj).Value + ")" + NewLine);
            }
            else if (obj is Asn1OctetString)
            {
                Asn1OctetString oct = (Asn1OctetString)obj;
                byte[] octets = oct.GetOctets();

                if (obj is BerOctetString)
                {
                    buf.Append(indent + "BER Octet String[" + octets.Length + "]" + NewLine);
                }
                else
                {
                    buf.Append(indent + "DER Octet String[" + octets.Length + "]" + NewLine);
                }

                if (verbose)
                {
                    buf.Append(DumpBinaryDataAsString(indent, octets));
                }
            }
            else if (obj is DerBitString)
            {
                DerBitString bitString = (DerBitString)obj;
                byte[] bytes = bitString.GetBytes();
                int padBits = bitString.PadBits;

                if (bitString is BerBitString)
                {
                    buf.Append(indent + "BER Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine);
                }
                else if (bitString is DLBitString)
                {
                    buf.Append(indent + "DL Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine);
                }
                else
                {
                    buf.Append(indent + "DER Bit String[" + bytes.Length + ", " + padBits + "]" + NewLine);
                }

                if (verbose)
                {
                    buf.Append(DumpBinaryDataAsString(indent, bytes));
                }
            }
            else if (obj is DerIA5String)
            {
                buf.Append(indent + "IA5String(" + ((DerIA5String)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerUtf8String)
            {
                buf.Append(indent + "UTF8String(" + ((DerUtf8String)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerPrintableString)
            {
                buf.Append(indent + "PrintableString(" + ((DerPrintableString)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerVisibleString)
            {
                buf.Append(indent + "VisibleString(" + ((DerVisibleString)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerBmpString)
            {
                buf.Append(indent + "BMPString(" + ((DerBmpString)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerT61String)
            {
                buf.Append(indent + "T61String(" + ((DerT61String)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerGraphicString)
            {
                buf.Append(indent + "GraphicString(" + ((DerGraphicString)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerVideotexString)
            {
                buf.Append(indent + "VideotexString(" + ((DerVideotexString)obj).GetString() + ")" + NewLine);
            }
            else if (obj is DerUtcTime)
            {
                buf.Append(indent + "UTCTime(" + ((DerUtcTime)obj).TimeString + ")" + NewLine);
            }
            else if (obj is DerGeneralizedTime)
            {
                buf.Append(indent + "GeneralizedTime(" + ((DerGeneralizedTime)obj).GetTime() + ")" + NewLine);
            }
            else if (obj is DerEnumerated)
            {
                DerEnumerated en = (DerEnumerated)obj;
                buf.Append(indent + "DER Enumerated(" + en.Value + ")" + NewLine);
            }
            else if (obj is DerExternal)
            {
                DerExternal ext = (DerExternal)obj;
                buf.Append(indent + "External " + NewLine);
                string tab = indent + Tab;

                if (ext.DirectReference != null)
                {
                    buf.Append(tab + "Direct Reference: " + ext.DirectReference.Id + NewLine);
                }
                if (ext.IndirectReference != null)
                {
                    buf.Append(tab + "Indirect Reference: " + ext.IndirectReference.ToString() + NewLine);
                }
                if (ext.DataValueDescriptor != null)
                {
                    AsString(tab, verbose, ext.DataValueDescriptor, buf);
                }
                buf.Append(tab + "Encoding: " + ext.Encoding + NewLine);
                AsString(tab, verbose, ext.ExternalContent, buf);
            }
            else
            {
                buf.Append(indent + obj.ToString() + NewLine);
            }
        }

        /**
         * dump out a DER object as a formatted string, in non-verbose mode
         *
         * @param obj the Asn1Encodable to be dumped out.
         * @return  the resulting string.
         */
        public static string DumpAsString(Asn1Encodable obj)
        {
            return DumpAsString(obj, false);
        }

        /**
         * Dump out the object as a string
         *
         * @param obj the Asn1Encodable to be dumped out.
         * @param verbose  if true, dump out the contents of octet and bit strings.
         * @return  the resulting string.
         */
        public static string DumpAsString(Asn1Encodable obj, bool verbose)
        {
            StringBuilder buf = new StringBuilder();
            AsString("", verbose, obj.ToAsn1Object(), buf);
            return buf.ToString();
        }

        private static string DumpBinaryDataAsString(string indent, byte[] bytes)
        {
            if (bytes.Length < 1)
                return "";

            indent += Tab;

            StringBuilder buf = new StringBuilder();

            for (int i = 0; i < bytes.Length; i += SampleSize)
            {
                int remaining = bytes.Length - i;
                int chunk = System.Math.Min(remaining, SampleSize);

                buf.Append(indent);
                buf.Append(Hex.ToHexString(bytes, i, chunk));
                for (int j = chunk; j < SampleSize; ++j)
                {
                    buf.Append("  ");
                }
                buf.Append(Tab);
                buf.Append(CalculateAscString(bytes, i, chunk));
                buf.Append(NewLine);
            }

            return buf.ToString();
        }

        private static string CalculateAscString(byte[] bytes, int off, int len)
        {
            StringBuilder buf = new StringBuilder();

            for (int i = off; i != off + len; i++)
            {
                char c = (char)bytes[i]; 
                if (c >= ' ' && c <= '~')
                {
                    buf.Append(c);
                }
            }

            return buf.ToString();
        }
    }
}
