# -*- coding: UTF-8 -*-
#    This file is part of arena.
#   
#    arena is free software: you can redistribute it and/or modify it
#    under the terms of the GNU General Public License as published by the
#    Free Software Foundation; either version 3 of the License or, at your
#    option, any later version.
#
#    arena is distributed in the hope that it will be useful, but
#    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABLITY
#    or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
#    License for more details.
#
#    You should have received a copy of the GNU General Public License along
#    with arena.  If not, see <http://www.gnu.org/licenses/>.

## @ file
#  This file defines a class that allows a string to be used as though it was
#  a stream.  The class basically wraps around the \p cStringIO library
#  provided by Python.

from Library.IO.Streams import AStream, IsStreamable
from cStringIO import StringIO

## Used to read the string stream contents from another stream
ssReadFormat = "={streamlen}c"

## A class that allows strings to be used as though they are streams.  
#
#  Both read and write operations are supported.
class AStringStream(AStream, IsStreamable):
    
    ## Construct a new string stream instance.  
    
    #  If one or more values are provided to the constructor, they are written 
    #  to the stream, but the stream position pointer is afterwards returned to 
    #  the beginning of the stream. 
    #    
    #  All values in the list of values are converted to strings. """
    def __init__(Self, *values):        
        AStream.__init__(Self)
        
        # Write any provided values to the stream
        if len(values) > 0:
            for value in values:
                Self._Handle.write(str(value))
                
            Self._Handle.seek(0)

    ## Destroy the string stream instance.
    #    
    #  This routine calls the AStringStream.close() method automatically.
    def __del__(Self):        
        Self.close()
        
    ## Initialize a new string stream instance.
    #    
    #  This routine installs a private attribute named "Handle" that represents
    #  the underlying StringIO object used by the stream.
    def init(Self):
        result = AStream.init(Self)
        
        if result == True:
            Self._Handle = StringIO()
        
        return result
        
    ## Close the stream
    def close(Self):
        if Self._Handle is not None:
            Self._Handle.close()
        
    ## Read the specified \p values from the stream.
    #
    #  The \p fmt parameter is assumed to contain the name of the first item
    #  to be read from the stream, even as the items in \p values are assumed
    #  to name each subsequent line read from the stream.
    #    
    #  \return This routine returns a dictionary object that contains one
    #  line of text from the stream for each item in \p values.  The dictionary
    #  object will also contain an item named \p "result" which indicates
    #  how many bytes were read from the stream.  If \p values and \p fmt are
    #  empty, this routine will return zero (0).
    def readFormat(Self, fmt, *values):        
        result = {"result": 0}
        
        if len(fmt) > 0:
            result[fmt] = Self._Handle.read(len(fmt))
            result["result"] += len(result[fmt])
        
        if len(values) > 0:
            for value in values:
                result[value] = Self._Handle.read(len(value))
                result["result"] += len(result[value])
                
                # Break if the end of the stream has been reached
                if len(value) == 0:
                    value = "\x19"
                    break        
                    
        return result
        
    ## Write each \p value to the stream.
    #    
    #  The \p fmt parameter is written first, followed by the items in \p
    #  values.  Each item is converted to a string before it is written.
    #
    #  \return This routine returns the total number of bytes written to the
    #  stream.
    def writeFormat(Self, fmt, *values):        
        result = 0
        
        if len(fmt) > 0:
            Self._Handle.write(fmt)
            result += len(fmt)
            
        if len(values) > 0:
            for value in values:
                valueString = str(value)
                  
                Self._Handle.write(valueString)
                result = result + len(valueString)
        
        return result
        
    ## Read the specified \p values from the stream.
    #
    #  Each item in the list of values is filled with a single line from the
    #  stream.
    #
    #  \return This routine returns a dictionary object with the same keys as
    #  those provided in \p values.  The value associated with each key is a
    #  single line from the stream.  The dictionary will contain one additional
    #  element: an item named \p "result" that indicates how many bytes were
    #  read from the stream.     
    def read(Self, **values):
        result = {"result": 0}
        
        if len(values) > 0:
            for key in values.iterkeys():
                result[key] = Self._Handle.read(len(values[key]))
                result["result"] += len(result[key])
                
                # Break for the end of the stream
                if len(result[key]) == 0:
                    result[key] = "\x19"
                    break
                       
        return result
    
    ## Write the specified \p values to the stream.
    #
    #  Each item in the list of \p values is converted to a string before it is
    #  written.
    #
    #  \return This routine returns the total number of bytes written to the
    #  stream.  If \p value is empty, the return value will be zero (0).
    def write(Self, *values):
        return Self.writeFormat("", *values)
    
    # IsStreamable implementation ----------------------------------------------
    ## Read the contents of the string stream from another stream.
    #
    #  Depending on the type of source stream provided, this routine has the
    #  potential to read the ENTIRE contents of that stream into memory.  Be    
    #  very careful when using this method!
    #
    #  \return This routine returns the total number of bytes read from \p
    #  Source.
    def readFrom(Self, Source):
        result = 0
        
        if isinstance(Source, AStream):
            streamLength = Source.length()
            if streamLength > 0:
                result = Source.readFormat(ssReadFormat, content)
                # Append the content to our string
                previousPosition = Self.position()
                Self._Handle.write(content)
                Self.moveTo(previousPosition)
        
        return result
        
    ## Write the contents of the string stream to another stream.
    #
    #  \return The total number of bytes written to \p Dest.
    def writeTo(Self, Dest):
        result = 0
        
        if isinstance(Dest, AStream):
            content = Self._Handle.getvalue()
            if len(content) > 0:
              result = Dest.write(content)
              
        return result
    
    # IsPrintable implementation -----------------------------------------------
    ## Return a string representation of the stream.
    #
    #  This method merely returns the contents of the underlying string.
    def toString(Self):
        return Self._Handle.getvalue()
    
    # Properties ---------------------------------------------------------------
    ## Retrieve the current position within the stream.
    #
    #  This is the position within the stream at which the next read or write
    #  operation will occur.  The position is measured as the number of bytes
    #  to the start of the stream, with the beginning of the stream at offset
    #  zero (0).
    def position(Self):        
        result = 0
        
        if Self._Handle is not None:
            result = Self._Handle.tell()
        
        return result
        
    ## Set the current position within the stream.
    #
    #  This is the position at which the next read or write operation will 
    #  occur.  The position within the stream is measured as the number of bytes
    #  to the start of the stream.  The beginning of the stream is always at
    #  offset zero (0).
    #
    #  If \p newPosition specifies a value less than zero, it is assumed to
    #  indicate an offset relative to the END of the stream (i.e., 
    #  \p newPosition + AStringStream.length + 1).  In this case, the end of
    #  the stream is always located at offset -1.  
    def moveTo(Self, newPosition):        
        if Self._Handle is not None:
            if newPosition < 0:
                newPosition += Self.length + 1
                
            if newPosition < 0:
                newPosition = 0
                
            Self._Handle.seek(newPosition)

    ## Retrieve the current length of the stream.
    #
    #  This value is the length of the underlying string.         
    def length(Self):
        contents = Self._Handle.getvalue()
        result = len(contents)
        del contents
        
        return result
        
