
// **********************************************************************
//
// Copyright (c) 2003-2016 ZeroC, Inc. All rights reserved.
//
// This copy of Ice is licensed to you under the terms described in the
// ICE_LICENSE file included in this distribution.
//
// **********************************************************************

namespace IceInternal
{

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

    //
    // Class to provide a System.IO.Stream interface on top of a BasicStream.
    // We use this to serialize arbitrary .NET serializable classes into
    // a Slice byte sequence.
    //
    // For input streams, this class is a wrapper around the BasicStream
    // class that passes all methods through.
    //
    // For output streams, we use a different stragegy:
    // Slice sequences are encoded on the wire as a count of elements, followed
    // by the sequence contents. For arbitrary .NET classes, we do not know how
    // big the sequence that is eventually written will be. To avoid excessive
    // data copying, this class mantains a private bytes_ array of 254 bytes and,
    // initially, writes data into that array. If more than 254 bytes end up being
    // written, we write a dummy sequence size of 255 (which occupies five bytes
    // on the wire) into the BasicStream and, once this class is disposed, patch
    // that size to match the actual size. Otherwise, if the bytes_ buffer contains
    // fewer than 255 bytes when this class is disposed, we write the sequence size
    // as a single byte, followed by the contents of the bytes_ buffer.
    //

    enum StreamType { Read, Write };

    public class StreamWrapper : System.IO.Stream, System.IDisposable
    {
        //
        // Writeable stream constructor
        //
        public StreamWrapper(BasicStream s)
        {
            type_ = StreamType.Write;
            s_ = s;
            spos_ = s.pos();
            bytes_ = new byte[254];
            pos_ = 0;
            length_ = 0;
        }

        //
        // Readable stream constructor
        //
        public StreamWrapper(int size, BasicStream s)
        {
            type_ = StreamType.Read;
            s_ = s;
            spos_ = 0;
            bytes_ = null;
            pos_ = 0;
            length_ = size;
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            Debug.Assert(buffer != null && offset >= 0 && count >= 0 && offset + count <= buffer.Length);
            try
            {
                s_.getBuffer().b.get(buffer, offset, count);
            }
            catch(System.Exception ex)
            {
                throw new IOException("could not read from stream", ex);
            }
            return count;
        }

        public override int ReadByte()
        {
            try
            {
                return s_.getBuffer().b.get();
            }
            catch(System.Exception ex)
            {
                throw new IOException("could not read from stream", ex);
            }
        }

        public override void Write(byte[] array, int offset, int count)
        {
            Debug.Assert(type_ == StreamType.Write);
            Debug.Assert(array != null && offset >= 0 && count >= 0 && offset + count <= array.Length);
            try
            {
                if(bytes_ != null)
                {
                    //
                    // If we can fit the data into the first 254 bytes, write it to bytes_.
                    //
                    if(count <= bytes_.Length - pos_)
                    {
                        System.Buffer.BlockCopy(array, offset, bytes_, pos_, count);
                        pos_ += count;
                        return;
                    }

                    s_.writeSize(255); // Dummy size, until we know how big the stream
                                       // really is and can patch the size.
                    if(pos_ > 0)
                    {
                        //
                        // Write the current contents of bytes_.
                        //
                        s_.expand(pos_);
                        s_.getBuffer().b.put(bytes_, 0, pos_);
                    }

                    bytes_ = null;
                }

                //
                // Write data passed by caller.
                //
                s_.expand(count);
                s_.getBuffer().b.put(array, offset, count);
                pos_ += count;
            }
            catch(System.Exception ex)
            {
                throw new IOException("could not write to stream", ex);
            }
        }

        public override void WriteByte(byte value)
        {
            Debug.Assert(type_ == StreamType.Write);
            try
            {
                if(bytes_ != null)
                {
                    //
                    // If we can fit the data into the first 254 bytes, write it to bytes_.
                    //
                    if(pos_ < bytes_.Length)
                    {
                        bytes_[pos_++] = value;
                        return;
                    }

                    s_.writeSize(255); // Dummy size, until we know how big the stream
                                       // really is and can patch the size.
                    if(pos_ > 0)
                    {
                        //
                        // Write the current contents of bytes_.
                        //
                        s_.expand(pos_);
                        s_.getBuffer().b.put(bytes_, 0, pos_);
                    }

                    bytes_ = null;
                }

                //
                // Write data passed by caller.
                //
                s_.expand(1);
                s_.getBuffer().b.put(value);
                pos_ += 1;
            }
            catch(System.Exception ex)
            {
                throw new IOException("could not write to stream", ex);
            }
        }

        public override bool CanRead
        {
            get
            {
                return true;
            }
        }

        public override bool CanWrite
        {
            get
            {
                return type_ == StreamType.Write;
            }
        }

        public override bool CanSeek
        {
            get
            {
                if(AssemblyUtil.runtime_ == AssemblyUtil.Runtime.Mono)
                {
                    //
                    // The Mono deserialization implementation has a bug that causes a call to Seek() such
                    // that the reading position is set to -1.
                    //
                    return false;
                }
                else
                {
                    return type_ == StreamType.Read;
                }
            }
        }

        public override void Flush()
        {
            try
            {
                if(bytes_ != null)
                {
                    Debug.Assert(pos_ <= bytes_.Length);
                    s_.pos(spos_);
                    s_.writeSize(pos_);
                    s_.expand(pos_);
                    s_.getBuffer().b.put(bytes_, 0, pos_);
                }
                else
                {
                    int currentPos = s_.pos();
                    s_.pos(spos_);
                    s_.writeSize(pos_); // Patch previously-written dummy value.
                    s_.pos(currentPos);
                }
            }
            catch(System.Exception ex)
            {
                throw new IOException("could not flush stream", ex);
            }
        }

        public override long Length
        {
            get
            {
                return length_;
            }
        }

        public override long Position
        {
            get
            {
                return pos_;
            }

            set
            {
                Seek(value, SeekOrigin.Begin);
            }
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            Debug.Assert(type_ != StreamType.Write);

            // Deliberately no size check here--positioning beyond the limit of the stream is legal.
            switch(origin)
            {
                case SeekOrigin.Begin:
                {
                    pos_ = (int)offset;
                    break;
                }
                case SeekOrigin.Current:
                {
                    pos_ += (int)offset;
                    break;
                }
                case SeekOrigin.End:
                {
                    pos_ = (int)length_ + (int)offset;
                    break;
                }
                default:
                {
                    Debug.Assert(false);
                    break;
                }
            }
            s_.pos(pos_);
            return pos_;
        }

        public override void SetLength(long value)
        {
            Debug.Assert(type_ == StreamType.Write && value >= 0);
            length_ = value;
        }

        private StreamType type_;
        private BasicStream s_;
        private int spos_;
        private byte[] bytes_;
        private int pos_;
        private long length_;
    }
}
