/// <summary>
    /// Returns the byte size of the message.
    /// </summary>
    public override int Size()
    {
        if (!_dirtySize)
        {
            return(_cachedSize);
        }

        _cachedSize = 0;

        // Address.
        _cachedSize += StringOscData.EvaluateByteCount(_address);

        // Tags.
        _cachedSize++;                            // Prefix;
        _cachedSize += _argInfo.Count;            // ASCII char tags.
        _cachedSize += 4 - (_cachedSize % 4);     // Followed by at least one trailing zero, multiple of four bytes.

        // Data, already multiple of four bytes.
        int argCount = _argInfo.Count;

        for (int i = 0; i < argCount; i++)
        {
            _cachedSize += _argInfo[i].size;
        }

        return(_cachedSize);
    }
    /// <summary>
    /// Set argument at specified index, expanding message capacity if necessary.
    /// </summary>
    public OscMessage Set(int index, string value)
    {
        OscArgInfo info           = new OscArgInfo(OscConst.tagStringByte, StringOscData.EvaluateByteCount(value));
        int        dataStartIndex = AdaptiveSet(index, info);

        if (!StringOscData.TryWriteTo(value, _argData, ref dataStartIndex))
        {
            Debug.Log(OscDebug.FailedWritingBytesWarning(this));
        }
        return(this);
    }
    public void Add(params object[] args)
    {
        // Adaptive Set info capacity.
        int infoStartIndex = _argInfo.Count;
        int newArgCount    = infoStartIndex + args.Length;

        if (newArgCount > _argInfo.Capacity)
        {
            _argInfo.Capacity = newArgCount;
        }

        // Get info and evaluate data byte count.
        int newArgsByteCount = 0;

        foreach (object arg in args)
        {
            byte tagByte = OscConverter.ToTagByte(arg);

            int argByteCount = 0;
            switch (tagByte)
            {
            case OscConst.tagFloatByte:
            case OscConst.tagIntByte:
            case OscConst.tagCharByte:
            case OscConst.tagColorByte:
            case OscConst.tagMidiByte:
                argByteCount = 4;
                break;

            case OscConst.tagDoubleByte:
            case OscConst.tagLongByte:
            case OscConst.tagTimetagByte:
                argByteCount = 8;
                break;

            case OscConst.tagStringByte:
                argByteCount = StringOscData.EvaluateByteCount((string)arg);
                break;

            case OscConst.tagBlobByte:
                argByteCount = BlobOscData.EvaluateByteCount((byte[])arg);
                break;
            }

            _argInfo.Add(new OscArgInfo(tagByte, argByteCount));
            newArgsByteCount += argByteCount;
        }

        // AdaptiveSet data capacity.
        int totalArgsByteCount = _argData.Count + newArgsByteCount;

        if (totalArgsByteCount > _argData.Capacity)
        {
            _argData.Capacity = totalArgsByteCount;
        }

        // Store arguments directly as bytes.
        int i = infoStartIndex;

        foreach (object arg in args)
        {
            switch (_argInfo[i++].tagByte)
            {
            case OscConst.tagFloatByte: new FourByteOscData((float)arg).AddTo(_argData); break;

            case OscConst.tagIntByte: new FourByteOscData((int)arg).AddTo(_argData); break;

            case OscConst.tagCharByte: new FourByteOscData((char)arg).AddTo(_argData); break;

            case OscConst.tagColorByte: new FourByteOscData((Color32)arg).AddTo(_argData); break;

            case OscConst.tagMidiByte: new FourByteOscData((OscMidiMessage)arg).AddTo(_argData); break;

            case OscConst.tagDoubleByte: new EightByteOscData((double)arg).AddTo(_argData); break;

            case OscConst.tagLongByte: new EightByteOscData((long)arg).AddTo(_argData); break;

            case OscConst.tagTimetagByte: new EightByteOscData((OscTimeTag)arg).AddTo(_argData); break;

            case OscConst.tagStringByte: StringOscData.AddTo((string)arg, _argData); break;

            case OscConst.tagBlobByte: BlobOscData.AddTo((byte[])arg, _argData); break;

            case OscConst.tagUnsupportedByte:
                // For unsupported tags, we don't attemt to store any data. But we warn the user.
                Debug.LogWarning("Type " + arg.GetType() + " is not supported.\n");                           // TODO warnings should be optional.
                break;
            }
        }
    }
    // Undocumented on purpose.
    public static bool TryReadFrom(byte[] data, ref int index, int size, ref OscMessage message)
    {
        int beginIndex = index;

        // If we are not provided with a message, then read the lossy hash and try reuse from the pool.
        if (message == null)
        {
            int hash = OscStringHash.Pack(data, index);
            message = OscPool.GetMessage(hash);
        }
        else
        {
            if (message._argInfo.Count > 0)
            {
                message.Clear();                                          // Ensure that arguments are cleared.
            }
        }

        // Address.
        string address = message._address;

        if (!StringOscData.TryReadFrom(data, ref index, ref address))
        {
            Debug.Log(OscDebug.FailedReadingBytesWarning(message));
            return(false);
        }
        message._address = address;

        // Tag prefix.
        if (data[index] != OscConst.tagPrefixByte)
        {
            StringBuilder sb = OscDebug.BuildText(message);
            sb.Append("Read failed. Tag prefix missing.\n");
            Debug.LogWarning(sb.ToString());
            return(false);
        }
        index++;

        // Argument tags.
        for (int i = index; i < data.Length && data[i] != 0; i++)
        {
            message._argInfo.Add(new OscArgInfo(data[i], 0));
        }
        index += message._argInfo.Count;

        // Followed by at least one trailing zero, multiple of four bytes.
        index += 4 - (index % 4);

        //Debug.Log( "READ: Args data start index: " + index );

        // Argument data info.
        int argDataByteCount = 0;

        for (int i = 0; i < message._argInfo.Count; i++)
        {
            byte tagByte      = message._argInfo[i].tagByte;
            int  argByteCount = 0;
            switch (tagByte)
            {
            case OscConst.tagNullByte:
            case OscConst.tagImpulseByte:
            case OscConst.tagTrueByte:
            case OscConst.tagFalseByte:
                break;

            case OscConst.tagFloatByte:
            case OscConst.tagIntByte:
            case OscConst.tagCharByte:
            case OscConst.tagColorByte:
            case OscConst.tagMidiByte:
                argByteCount = 4;
                break;

            case OscConst.tagDoubleByte:
            case OscConst.tagLongByte:
            case OscConst.tagTimetagByte:
                argByteCount = 8;
                break;

            case OscConst.tagStringByte:
            case OscConst.tagSymbolByte:
                argByteCount = StringOscData.EvaluateByteCount(data, index + argDataByteCount);
                break;

            case OscConst.tagBlobByte:
                BlobOscData.TryEvaluateByteCount(data, index + argDataByteCount, out argByteCount);
                break;

            default:
                StringBuilder sb = OscDebug.BuildText(message);
                sb.Append("Read failed. Tag '"); sb.Append((char)tagByte); sb.Append("' is not supported\n");
                Debug.LogWarning(sb.ToString());
                return(false);
            }
            message._argInfo[i] = new OscArgInfo(tagByte, argByteCount);
            //Debug.Log( "i; " + i + ", info: " + message._argInfo[i] );
            argDataByteCount += argByteCount;
        }

        // AdaptiveSet data list.
        if (message._argData.Capacity < argDataByteCount)
        {
            message._argData.Capacity = argDataByteCount;
        }

        // Read data.
        for (int i = 0; i < argDataByteCount; i++)
        {
            message._argData.Add(data[index++]);
        }

        // Cache byte count.
        message._cachedSize = index - beginIndex;
        message._dirtySize  = false;

        return(true);
    }