/*******************************************************************************

        @file AbstractWriter.d
        

        Copyright (C) 2004 Kris Bell
        
        This software is provided 'as-is', without any express or implied
        warranty. In no event will the authors be held liable for damages
        of any kind arising from the use of this software.
        
        Permission is hereby granted to anyone to use this software for any 
        purpose, including commercial applications, and to alter it and/or 
        redistribute it freely, subject to the following restrictions:
        
        1. The origin of this software must not be misrepresented; you must 
           not claim that you wrote the original software. If you use this 
           software in a product, an acknowledgment within documentation of 
           said product would be appreciated but is not required.

        2. Altered source versions must be plainly marked as such, and must 
           not be misrepresented as being the original software.

        3. This notice may not be removed or altered from any distribution
           of the source.


                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


        @version        Initial version, October 2004      
        @author         Kris

*******************************************************************************/

module mango.io.AbstractWriter;

private import  mango.io.Exception;

public  import  mango.io.model.IWriter,
                mango.io.model.IBuffer,
                mango.io.model.IConduit;

/*******************************************************************************

        Writer base-class. Writers provide the means to append formatted 
        data to an IBuffer, and expose a convenient method of handling a
        variety of data types. In addition to writing native types such
        as integer and char[], writers also process any class which has
        implemented the IWritable interface (one method).

        Writers support both a C++ iostream type syntax, along with a put()
        syntax. Operations may be chained back-to-back.

        All writers support the full set of native data types, plus a
        selection of array types. The latter can be configured to produce
        either a copy (.dup) of the buffer content, or a slice. See class
        SimpleAllocator, BufferAllocator and SliceAllocator for more on 
        this topic.

        The code below illustrates basic operation upon a memory buffer:
        
        @code
        Buffer buf = new Buffer (256);

        // map same buffer into both reader and writer
        IReader r = new Reader(buf);
        IWriter w = new Writer(buf);

        int i = 10;
        long j = 20;
        double d = 3.14159;
        char[] c = "fred";

        // write data types out
        w << c << i << j << d;

        // read them back again
        r >> c >> i >> j >> d;

        // reset
        buf.clear();

        // same thing again, but using put() syntax instead
        w.put(c).put(i).put(j).put(d);
        r.get(c).get(i).get(j).get(d);

        @endcode

        Writers may also be used with any class implementing the IWritable
        interface. See PickleReader for an example of how this can be put
        to good use.
        
*******************************************************************************/

class AbstractWriter : IWriter
{     
        alias opShl put;
          
        /***********************************************************************

        ***********************************************************************/

        union Encoder
        {
                struct {
                       BufferEncoder	int1,
                                        int8,
                                        int16,
                                        int32,
                                        int64,
                                        int8u,
                                        int16u,
                                        int32u,
                                        int64u,
                                        float32,
                                        float64,
                                        float80,
					char8,
                                        char16,
                                        char32;
                       }
                BufferEncoder[15]	encoders;
        }

        // public newline adaptor
        static INewlineWriter           newline;                

        // a couple of pre-constructed exceptions 
        protected static IOException    ovf;

        protected IBuffer               buffer;

        protected Encoder		encode;

        private bool                    prefixArray = true;

        /***********************************************************************
        
                Return the name of this writer

        ***********************************************************************/

        abstract char[] toString ();

        /***********************************************************************
        
                Construct some static exception instances, and create the
                public 'newline' instance.

        ***********************************************************************/

        static this ()
        {
                newline = new NewlineWriter;

                ovf = new IOException  ("output buffer too small");
        }

        /***********************************************************************
        
                Construct a Writer upon the provided IBuffer. All formatted
                output will be appended to this buffer.

        ***********************************************************************/

        this (IBuffer buffer)
        {
                this.buffer = buffer;
        }
     
        /***********************************************************************
        
                Return the associated buffer

        ***********************************************************************/

        final IBuffer getBuffer ()
        {     
                return buffer;
        }

        /***********************************************************************
        
                Bind an IEncoder to the writer. Encoders are intended to
                be used as a conversion mechanism between various character
                representations (encodings), or the translation of any data
                type from one representation to another. Each data type may
                be configured with a distinct encoder, covering all native
                types (15 in total).

                An appropriate encoder set should be attached to each 
                IWriter, and thus be available for subsequent use. A raw 
                binary implementation is attached by default (no encoding).

                See module mango.icu.UMango for an example of encoder 
                implementation -- those classes bind the ICU converters 
                to this IO package.

        ***********************************************************************/

        final void setEncoder (IEncoder e) 
        {
                encode.encoders[e.type] = e.bind (this);
        }

        /***********************************************************************
        
                Flush the output of this writer. Returns false if the 
                operation failed, true otherwise.

        ***********************************************************************/

        IWriter flush ()
        {  
                buffer.flush ();
                return this;
        }

        /***********************************************************************
        
                Output a newline. Do this indirectly so that it can be 
                intercepted by subclasses.

        ***********************************************************************/

        IWriter cr ()
        {
                return put (newline);
        }

        /***********************************************************************

        ***********************************************************************/

        void enableArrayPrefix (bool on)
        {
                prefixArray = on;
        }

        /***********************************************************************
        
                Write a class to the current buffer-position
                
        ***********************************************************************/

        private final uint length (uint len)
        {
                if (prefixArray)
                    put (len);
                return len;
        }

        /***********************************************************************
        
                Write a class to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (IWritable x) 
        {
                assert (x);
                x.write (this); 
                return this;
        }

        /***********************************************************************
        
                Write a boolean value to the current buffer-position    
                
        ***********************************************************************/

        IWriter opShl (bool x)
        {
                encode.int1 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned byte value to the current buffer-position     
                                
        ***********************************************************************/

        IWriter opShl (ubyte x)
        {
                encode.int8u (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a byte value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (byte x)
        {
                encode.int8 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned short value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (ushort x)
        {
                encode.int16u (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a short value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (short x)
        {
                encode.int16 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a unsigned int value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (uint x)
        {
                encode.int32u (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an int value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (int x)
        {
                encode.int32 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned long value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (ulong x)
        {
                encode.int64u (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a long value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (long x)
        {
                encode.int64 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a float value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (float x)
        {
                encode.float32 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a double value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (double x)
        {
                encode.float64 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a real value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (real x)
        {
                encode.float80 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a char value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (char x)
        {
                encode.char8 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a wide char value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (wchar x)
        {
                encode.char16 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a double char value to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (dchar x)
        {
                encode.char32 (&x, x.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a byte array to the current buffer-position     
                                
        ***********************************************************************/

        IWriter opShl (byte[] x)
        {
                encode.int8 (x, length (x.length) * byte.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned byte array to the current buffer-position     
                                
        ***********************************************************************/

        IWriter opShl (ubyte[] x)
        {
                encode.int8u (x, length (x.length) * ubyte.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a short array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (short[] x)
        {
                encode.int16 (x, length (x.length) * short.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned short array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (ushort[] x)
        {
                encode.int16u (x, length (x.length) * ushort.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an int array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (int[] x)
        {
                encode.int32 (x, length (x.length) * int.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned int array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (uint[] x)
        {
                encode.int32u (x, length (x.length) * uint.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a long array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (long[] x)
        {
                encode.int64 (x, length (x.length) * long.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write an unsigned long array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (ulong[] x)
        {
                encode.int64u (x, length (x.length) * ulong.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a float array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (float[] x)
        {
                encode.float32 (x, length (x.length) * float.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a double array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (double[] x)
        {
                encode.float64 (x, length (x.length) * double.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a real array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (real[] x)
        {
                encode.float80 (x, length (x.length) * real.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a char array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShl (char[] x) 
        {
                encode.char8 (x, length (x.length) * char.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a char array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShlw (wchar[] x) 
        {
                encode.char16 (x, length (x.length) * wchar.sizeof);
                return this;
        }

        /***********************************************************************
        
                Write a char array to the current buffer-position
                
        ***********************************************************************/

        IWriter opShld (dchar[] x)
        {
                encode.char32 (x, length (x.length) * dchar.sizeof);
                return this;
        }
}


/*******************************************************************************

        A class to handle newline output. One might reasonable expect to 
        emit a char[] for newlines; FileSystem.NewlineString for example.
        Turns out that it's much more efficient to intercept line-breaks
        when they're implemented in a more formal manner (such as this).

        For example, ColumnWriter() and TextWriter() both must intercept 
        newline output so they can adjust formatting appropriately. It is 
        much more efficient for such writers to intercept the IWritable 
        put() method instead of scanning each char[] for the various \\n 
        combinations.
        
        Please use the INewlineWriter interface for emitting newlines.

*******************************************************************************/

private import mango.io.FileSystem;

class NewlineWriter : INewlineWriter
{
        private char[]  fmt;

        /***********************************************************************

                Construct a default newline, using the char[] defined 
                by FileSystem.NewlineString
        
        ***********************************************************************/

        this ()
        {
                this (FileSystem.NewlineString);
        }

        /***********************************************************************
        
                Construct a newline using the provided character array

        ***********************************************************************/

        this (char[] fmt)
        {
                this.fmt = fmt;
        }

        /***********************************************************************
        
                Write this newline through the provided writer. This makes
                NewlineWriter IWritable compatible.

        ***********************************************************************/

        void write (IWriter w)
        {
                w.put (fmt);
        }     
}
