// ========================================================================
// Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package org.mortbay.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * @author gregw
 *  
 */
public abstract class AbstractBuffer implements Buffer
{
    protected final static String 
    __IMMUTABLE = "IMMUTABLE", 
    __READONLY = "READONLY",
    __READWRITE = "READWRITE", 
    __VOLATILE = "VOLATILE";
    
    protected int _access;
    protected boolean _volatile;

    protected int _get;
    protected int _put;
    protected int _hash;
    protected int _hashGet;
    protected int _hashPut;
    protected int _mark;
    protected String _string;
    protected View _view;

    /**
     * Constructor for BufferView
     * 
     * @param access 0==IMMUTABLE, 1==READONLY, 2==READWRITE
     */
    public AbstractBuffer(int access, boolean isVolatile)
    {
        if (access == IMMUTABLE && isVolatile)
                throw new IllegalArgumentException("IMMUTABLE && VOLATILE");
        setMarkIndex(-1);
        _access = access;
        _volatile = isVolatile;
    }

    /*
     * @see org.mortbay.io.Buffer#toArray()
     */
    public byte[] asArray()
    {
        byte[] bytes = new byte[length()];
        byte[] array = array();
        if (array != null)
            Portable.arraycopy(array, getIndex(), bytes, 0, bytes.length);
        else
            peek(getIndex(), bytes, 0, length());
        return bytes;
    }

    public ByteArrayBuffer duplicate(int access)
    {
        Buffer b=this.buffer();
        if (b instanceof Buffer.CaseInsensitve)
            return new ByteArrayBuffer.CaseInsensitive(asArray(), 0, length(),access);
        else
            return new ByteArrayBuffer(asArray(), 0, length(), access);
    }
    
    /*
     * @see org.mortbay.io.Buffer#asNonVolatile()
     */
    public Buffer asNonVolatileBuffer()
    {
        if (!isVolatile()) return this;
        return duplicate(_access);
    }

    public Buffer asImmutableBuffer()
    {
        if (isImmutable()) return this;
        return duplicate(IMMUTABLE);
    }

    /*
     * @see org.mortbay.util.Buffer#asReadOnlyBuffer()
     */
    public Buffer asReadOnlyBuffer()
    {
        if (isReadOnly()) return this;
        return new View(this, markIndex(), getIndex(), putIndex(), READONLY);
    }

    public Buffer asMutableBuffer()
    {
        if (!isImmutable()) return this;
        
        Buffer b=this.buffer();
        if (b.isReadOnly())
        {
            return duplicate(READWRITE);
        }
        return new View(b, markIndex(), getIndex(), putIndex(), _access);
    }

    public Buffer buffer()
    {
        return this;
    }

    public void clear()
    {
        setMarkIndex(-1);
        setGetIndex(0);
        setPutIndex(0);
    }

    public void compact()
    {
        if (isReadOnly()) throw new IllegalStateException(__READONLY);
        int s = markIndex() >= 0 ? markIndex() : getIndex();
        if (s > 0)
        {
            byte array[] = array();
            int length = putIndex() - s;
            if (length > 0)
            {
                if (array != null)
                    Portable.arraycopy(array(), s, array(), 0, length);
                else
                    poke(0, peek(s, length));
            }
            if (markIndex() > 0) setMarkIndex(markIndex() - s);
            setGetIndex(getIndex() - s);
            setPutIndex(putIndex() - s);
        }
    }

    public boolean equals(Object obj)
    {
        if (obj==this)
            return true;
        
        // reject non buffers;
        if (obj == null || !(obj instanceof Buffer)) return false;
        Buffer b = (Buffer) obj;

        if (this instanceof Buffer.CaseInsensitve ||  b instanceof Buffer.CaseInsensitve)
            return equalsIgnoreCase(b);
        
        // reject different lengths
        if (b.length() != length()) return false;

        // reject AbstractBuffer with different hash value
        if (_hash != 0 && obj instanceof AbstractBuffer)
        {
            AbstractBuffer ab = (AbstractBuffer) obj;
            if (ab._hash != 0 && _hash != ab._hash) return false;
        }

        // Nothing for it but to do the hard grind.
        int get=getIndex();
        int bi=b.putIndex();
        for (int i = putIndex(); i-->get;)
        {
            byte b1 = peek(i);
            byte b2 = b.peek(--bi);
            if (b1 != b2) return false;
        }
        return true;
    }

    public boolean equalsIgnoreCase(Buffer b)
    {
        if (b==this)
            return true;
        
        // reject different lengths
        if (b.length() != length()) return false;

        // reject AbstractBuffer with different hash value
        if (_hash != 0 && b instanceof AbstractBuffer)
        {
            AbstractBuffer ab = (AbstractBuffer) b;
            if (ab._hash != 0 && _hash != ab._hash) return false;
        }

        // Nothing for it but to do the hard grind.
        int get=getIndex();
        int bi=b.putIndex();
        
        byte[] array = array();
        byte[] barray= b.array();
        if (array!=null && barray!=null)
        {
            for (int i = putIndex(); i-->get;)
            {
                byte b1 = array[i];
                byte b2 = barray[--bi];
                if (b1 != b2)
                {
                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
                    if (b1 != b2) return false;
                }
            }
        }
        else
        {
            for (int i = putIndex(); i-->get;)
            {
                byte b1 = peek(i);
                byte b2 = b.peek(--bi);
                if (b1 != b2)
                {
                    if ('a' <= b1 && b1 <= 'z') b1 = (byte) (b1 - 'a' + 'A');
                    if ('a' <= b2 && b2 <= 'z') b2 = (byte) (b2 - 'a' + 'A');
                    if (b1 != b2) return false;
                }
            }
        }
        return true;
    }

    public byte get()
    {
        return peek(_get++);
    }

    public int get(byte[] b, int offset, int length)
    {
        int gi = getIndex();
        int l=length();
        if (l==0)
            return -1;
        
        if (length>l)
            length=l;
        
        length = peek(gi, b, offset, length);
        if (length>0)
            setGetIndex(gi + length);
        return length;
    }

    public Buffer get(int length)
    {
        int gi = getIndex();
        Buffer view = peek(gi, length);
        setGetIndex(gi + length);
        return view;
    }

    public final int getIndex()
    {
        return _get;
    }

    public boolean hasContent()
    {
        return _put > _get;
    }
    
    public int hashCode()
    {
        if (_hash == 0 || _hashGet!=_get || _hashPut!=_put) 
        {
            int get=getIndex();
            byte[] array = array();
            if (array==null)
            {
                for (int i = putIndex(); i-- >get;)
                {
                    byte b = peek(i);
                    if ('a' <= b && b <= 'z') 
                        b = (byte) (b - 'a' + 'A');
                    _hash = 31 * _hash + b;
                }
            }
            else
            {
                for (int i = putIndex(); i-- >get;)
                {
                    byte b = array[i];
                    if ('a' <= b && b <= 'z') 
                        b = (byte) (b - 'a' + 'A');
                    _hash = 31 * _hash + b;
                }
            }
            if (_hash == 0) 
                _hash = -1;
            _hashGet=_get;
            _hashPut=_put;
            
        }
        return _hash;
    }

    public boolean isImmutable()
    {
        return _access <= IMMUTABLE;
    }

    public boolean isReadOnly()
    {
        return _access <= READONLY;
    }

    public boolean isVolatile()
    {
        return _volatile;
    }

    public int length()
    {
        return _put - _get;
    }

    public void mark()
    {
        setMarkIndex(_get - 1);
    }

    public void mark(int offset)
    {
        setMarkIndex(_get + offset);
    }

    public int markIndex()
    {
        return _mark;
    }

    public byte peek()
    {
        return peek(_get);
    }

    public Buffer peek(int index, int length)
    {
        if (_view == null)
        {
            _view = new View(this, -1, index, index + length, isReadOnly() ? READONLY : READWRITE);
        }
        else
        {
            _view.update(this.buffer());
            _view.setMarkIndex(-1);
            _view.setGetIndex(0);
            _view.setPutIndex(index + length);
            _view.setGetIndex(index);
            
        }
        return _view;
    }

    public int poke(int index, Buffer src)
    {
        _hash=0;
        /* 
        if (isReadOnly()) 
            throw new IllegalStateException(__READONLY);
        if (index < 0) 
            throw new IllegalArgumentException("index<0: " + index + "<0");
        */
        
        int length=src.length();
        if (index + length > capacity())
        {
            length=capacity()-index;
            /*
            if (length<0)
                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
            */
        }
        
        byte[] src_array = src.array();
        byte[] dst_array = array();
        if (src_array != null && dst_array != null)
            Portable.arraycopy(src_array, src.getIndex(), dst_array, index, length);
        else if (src_array != null)
        {
            int s=src.getIndex();
            for (int i=0;i<length;i++)
                poke(index++,src_array[s++]);
        }
        else if (dst_array != null)
        {
            int s=src.getIndex();
            for (int i=0;i<length;i++)
                dst_array[index++]=src.peek(s++);
        }
        else
        {
            int s=src.getIndex();
            for (int i=0;i<length;i++)
                poke(index++,src.peek(s++));
        }
        
        return length;
    }
    

    public int poke(int index, byte[] b, int offset, int length)
    {
        _hash=0;
        /*
        if (isReadOnly()) 
            throw new IllegalStateException(__READONLY);
        if (index < 0) 
            throw new IllegalArgumentException("index<0: " + index + "<0");
        */
        if (index + length > capacity())
        {
            length=capacity()-index;
            /* if (length<0)
                throw new IllegalArgumentException("index>capacity(): " + index + ">" + capacity());
            */
        }
        
        byte[] dst_array = array();
        if (dst_array != null)
            Portable.arraycopy(b, offset, dst_array, index, length);
        else
        {
            int s=offset;
            for (int i=0;i<length;i++)
                poke(index++,b[s++]);
        }
        return length;
    }

    public int put(Buffer src)
    {
        int pi = putIndex();
        int l=poke(pi, src);
        setPutIndex(pi + l);
        return l;
    }

    public void put(byte b)
    {
        int pi = putIndex();
        poke(pi, b);
        setPutIndex(pi + 1);
    }

    public int put(byte[] b, int offset, int length)
    {
        int pi = putIndex();
        int l = poke(pi, b, offset, length);
        setPutIndex(pi + l);
        return l;
    }
    
    public int put(byte[] b)
    {
        int pi = putIndex();
        int l = poke(pi, b, 0, b.length);
        setPutIndex(pi + l);
        return l;
    }

    public final int putIndex()
    {
        return _put;
    }

    public void reset()
    {
        if (markIndex() >= 0) setGetIndex(markIndex());
    }

    public void rewind()
    {
        setGetIndex(0);
        setMarkIndex(-1);
    }

    public void setGetIndex(int getIndex)
    {
        /* bounds checking
        if (isImmutable()) 
            throw new IllegalStateException(__IMMUTABLE);
        if (getIndex < 0)
            throw new IllegalArgumentException("getIndex<0: " + getIndex + "<0");
        if (getIndex > putIndex())
            throw new IllegalArgumentException("getIndex>putIndex: " + getIndex + ">" + putIndex());
         */
        _get = getIndex;
        _hash=0;
    }

    public void setMarkIndex(int index)
    {
        /*
        if (index>=0 && isImmutable()) 
            throw new IllegalStateException(__IMMUTABLE);
        */
        _mark = index;
    }

    public void setPutIndex(int putIndex)
    {
        /* bounds checking
        if (isImmutable()) 
            throw new IllegalStateException(__IMMUTABLE);
        if (putIndex > capacity())
                throw new IllegalArgumentException("putIndex>capacity: " + putIndex + ">" + capacity());
        if (getIndex() > putIndex)
                throw new IllegalArgumentException("getIndex>putIndex: " + getIndex() + ">" + putIndex);
         */
        _put = putIndex;
        _hash=0;
    }

    public int skip(int n)
    {
        if (length() < n) n = length();
        setGetIndex(getIndex() + n);
        return n;
    }

    public Buffer slice()
    {
        return peek(getIndex(), length());
    }

    public Buffer sliceFromMark()
    {
        return sliceFromMark(getIndex() - markIndex() - 1);
    }

    public Buffer sliceFromMark(int length)
    {
        if (markIndex() < 0) return null;
        Buffer view = peek(markIndex(), length);
        setMarkIndex(-1);
        return view;
    }

    public int space()
    {
        return capacity() - _put;
    }

    public String toDetailString()
    {
        StringBuffer buf = new StringBuffer();
        buf.append("[");
        buf.append(super.hashCode());
        buf.append(",");
        buf.append(this.array().hashCode());
        buf.append(",m=");
        buf.append(markIndex());
        buf.append(",g=");
        buf.append(getIndex());
        buf.append(",p=");
        buf.append(putIndex());
        buf.append(",c=");
        buf.append(capacity());
        buf.append("]={");
        if (markIndex() >= 0)
        {
            for (int i = markIndex(); i < getIndex(); i++)
            {
                char c = (char) peek(i);
                if (Character.isISOControl(c))
                {
                    buf.append(c < 16 ? "\\0" : "\\");
                    buf.append(Integer.toString(c, 16));
                }
                else
                    buf.append(c);
            }
            buf.append("}{");
        }
        int count = 0;
        for (int i = getIndex(); i < putIndex(); i++)
        {
            char c = (char) peek(i);
            if (Character.isISOControl(c))
            {
                buf.append(c < 16 ? "\\0" : "\\");
                buf.append(Integer.toString(c, 16));
            }
            else
                buf.append(c);
            if (count++ == 50)
            {
                if (putIndex() - i > 20)
                {
                    buf.append(" ... ");
                    i = putIndex() - 20;
                }
            }
        }
        buf.append('}');
        return buf.toString();
    }

    /* ------------------------------------------------------------ */
    public String toString()
    {
        if (isImmutable())
        {
            if (_string == null) 
                _string = new String(asArray(), 0, length());
            return _string;
        }
        return new String(asArray(), 0, length());
    }

    /* ------------------------------------------------------------ */
    public String toDebugString()
    {
        return getClass()+"@"+super.hashCode();
    }

    /* ------------------------------------------------------------ */
    public void writeTo(OutputStream out)
    	throws IOException
    {
        byte[] array = array();
        
        if (array!=null)
        {
            out.write(array,getIndex(),length());
        }
        else
        {
            int len = this.length();
            byte[] buf=new byte[len>1024?1024:len];
            int offset=_get;
            while (len>0)
            {
                int l=peek(offset,buf,0,len>buf.length?buf.length:len);
                out.write(buf,0,l);
                offset+=l;
                len-=l;
            }
        } 
        clear();
    }
    
    /* ------------------------------------------------------------ */
    public int readFrom(InputStream in,int max) throws IOException
    {
        byte[] array = array();
        int s=space();
        if (s>max)
            s=max;

        if (array!=null)
        {
            int l=in.read(array,_put,s);
            if (l>0)
                _put+=l;
            return l;
        }
        else
        {
            byte[] buf=new byte[s>1024?1024:s];
            int total=0;
            while (s>0)
            {
                int l=in.read(buf,0,buf.length);
                if (l<0)
                    return total>0?total:-1;
                int p=put(buf,0,l);
                assert l==p;
                s-=l;
            }
            return total; 
        }
    }
}
