/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program 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 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY 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 this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/

package com.sap.dbtech.util;

import java.io.*;

import com.sap.dbtech.jdbc.translators.ConversionExceptionSapDB;

/**
 *
 *
 */
public class StructuredBytes
    implements StructuredMem, Traceable
{
    public static final String sapdbEncodingC = "ISO8859_1";

    protected byte [] data;
    protected int ptrOffs;
    protected int byteSize;

    protected final static byte blank_C = 32;
    public final static char[] hexDigits_C = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static final int fillBufSizeC = 1024;
    private static final int unicodeWidthC = 2;
    static byte [] zeroBytes = new byte [fillBufSizeC];
    static byte [] blankBytes = new byte [fillBufSizeC];
    static byte [] blankUnicodeBytes = new byte [fillBufSizeC * 2];
    static {

        for (int i = 0; i < fillBufSizeC; i+=2) {
            zeroBytes  [i] = zeroBytes [i+1] = 0;
            blankBytes [i] = blankBytes[i+1] = blank_C;
            blankUnicodeBytes [i] = 0;
            blankUnicodeBytes [i + 1] = blank_C;
        }
    }




    /**
     * Access <i>data</i> as StructuredMem
     * @param data byte[]
     */
    public StructuredBytes (
        byte [] data)
    {
        this.data=data;
        this.ptrOffs=0;
        this.byteSize=data.length;
    }
    
    /**
     * Access <i>data</i> as StructuredMem
     * @param data byte[]
     * @param offset int
     */
    public StructuredBytes (
        byte [] data,
        int offset)
    {
        this.data = data;
        this.ptrOffs = offset;
        this.byteSize = data.length - ptrOffs;
    }


    /**
     * Create a new StructuredMem of <i>size<i> bytes
     * @param size int
     */
    public StructuredBytes (
        int size)
    {
        this.data=new byte[size];
        this.byteSize=size;
    }
    
    /**
     *
     * @return byte[]
     */
    public byte[] bytes ()
    {
        return data;
    }
    /**
     *
     * @return byte[]
     * @param offset int
     * @param len int
     */
    public byte[] getBytes (
        int offset,
        int len)
    {
        byte [] result = new byte [len];
        System.arraycopy (data, offset + this.ptrOffs, result, 0, len);
        return result;
    }
    /**
     *
     * @return int[]
     * @param offset int
     * @param len int
     */
    public int[] getIntArray (
        int offset,
        int len)
    {
        int [] result = new int [len];
        for (int i = 0; i < len; i++) {
          result[i] = this.getUInt1(offset+i);
        }
        return result;
    }
    /**
     * Read a signed one byte integer from <i>offset</i>
     * @return byte
     * @param offset int
     */
    public byte getInt1 (
        int offset)
    {
        return data [offset + this.ptrOffs];
    }
    /**
     * Read an unsigned one byte integer from <i>offset</i>
     * @return int
     * @param offset int
     */
    public int getUInt1 (
        int offset)
    {
        int result = data [offset + this.ptrOffs];

        if (result < 0) {
            result += 256;
        }
        return result;
    }
    /**
     * Read a signed two byte integer from <i>offset</i>
     * @return int
     * @param offset int
     */
    public int getInt2 (
        int offset)
    {
        int result;
        int ofs=offset+this.ptrOffs;
        
        int lowerByte = data [ofs + 1];
        if (lowerByte < 0) {
            lowerByte += 256;
        }
        return (data [ofs] * 256) + lowerByte;
    }
    
    /**
     * Read a signed four byte integer from <i>offset</i>
     * @return int
     * @param offset int
     */
    public int getInt4 (
        int offset)
    {
        byte[] d=this.data;
        int ofs=offset+this.ptrOffs;
        int result=0;
        
        for (int i = 0; i < 4; ++i) {
            int oneByte = d[ofs + i];
            if (oneByte < 0) {
                oneByte += 256;
            }
            result = (result * 256) + oneByte;
        }
        
        return result;
    }

    /**
     *
     * @return com.sap.dbtech.util.StructuredBytes
     * @param offset int
     */
    public StructuredMem getPointer (
        int offset)
    {
        return new StructuredBytes (this.data, this.ptrOffs + offset);
    }
    /**
     *
     * @return java.lang.String
     * @param offset int
     * @param len int
     */
    public String getString (
        int offset,
        int len)
    {
        try {
            return new String (data, offset + this.ptrOffs, len, sapdbEncodingC);
        }
        catch (java.io.UnsupportedEncodingException exc) {
            return new String (data, offset + this.ptrOffs, len);
        }
    }
    
    /**
     *
     * @return byte[]
     * @param offset int
     * @param len int
     */
    public byte [] getStrippedBytes (
        int offset,
        int len)
    {
        while ((len > 0) && (data [offset + this.ptrOffs + len - 1] == 0)) {
            --len;
        }
        return getBytes (offset, len);
    }
    /**
    *
    * @param offset int
    * @param len int
    */
   public byte [] getStrippedUnicodeString (
       int offset,
       int len)
   {
       while ((len > 1) && (data [offset + this.ptrOffs + len - 1] == 0x20)&& (data [offset + this.ptrOffs + len - 2] == 0x00)) {
           len-=2;
       }
       return getBytes(offset, len);

       
//       String trimmedString;
//
//       trimmedString = StringUtil.trimLeft (this.getString (offset, len));
//       return trimmedString;
   }
    /**
     *
     * @param offset int
     * @param len int
     */
    public String getStrippedString (
        int offset,
        int len)
    {
        while ((len > 0) && (data [offset + this.ptrOffs + len - 1] == 0x20)) {
            --len;
        }
        return getString(offset, len);

        
//        String trimmedString;
//
//        trimmedString = StringUtil.trimLeft (this.getString (offset, len));
//        return trimmedString;
    }
    /**
     * reads a bigendian unicode character from <i>offset</i>
     *
     * @param offset
     *
     * @return char
     */
    public char
    getBigUnicodeChar (
        int offset)
    {
        int realOffset = this.ptrOffs + offset;

        int high = this.data [realOffset];
        if (high < 0) {
            high += 256;
        }
        int low = this.data [realOffset + 1];
        if (low < 0) {
            low += 256;
        }
        int result = (high << 8) + low;
        return (char) result;
    }
    /**
     * converts <i>lenInBytes</i> bytes
     * into <i>lenInBytes</i> chars.
     *
     * @param offset
     *
     * @param lenInBytes
     *
     * @return char []
     */
    public char []
    getBigUnicode (
        int offset,
        int lenInBytes)
    {
        int realOffset = this.ptrOffs + offset;

        return UnicodeUtil.bigUnicode2Chars (this.data,
            realOffset, lenInBytes);
    }
    /**
     *
     * @param offset int
     */
    public void moveBase (
        int offset)
    {
        this.ptrOffs += offset;
        if (this.size () < 0) {
            throw new ArrayIndexOutOfBoundsException ();
        }
        this.byteSize -= offset;
    }
    /**
     *
     * @param value byte[]
     * @param offset int
     */
    public void putBytes (
        byte [] value,
        int offset)
    {
        System.arraycopy (value, 0, data, offset + this.ptrOffs, value.length);
        return;
    }
    /**
     *
     * @param value byte[]
     * @param offset int
     * @param len int
     */
    private void putBytes (
        byte [] value,
        int offset,
        int len,
        byte [] filler)
    {
        int copyLen = value.length;
        int fillLen = 0;

        if (copyLen > len) {
            copyLen = len;
        }
        else if (copyLen <  len) {
            fillLen = len - copyLen;
        }
        System.arraycopy (value, 0, data, offset + this.ptrOffs, copyLen);
        if (fillLen > 0) {
            this.fill (offset + copyLen, fillLen, filler);
        }
        return;
    }
    /**
     *
     * @param value byte[]
     * @param offset int
     * @param len int
     */
    public void putBytes (
        byte [] value,
        int offset,
        int len)
    {
        this.putBytes (value, offset, len, zeroBytes);
    }
    /**
     *
     * @param value byte[]
     * @param offset int
     * @param len int
     */
    public void putStringBytes (
        byte [] value,
        int offset,
        int len)
    {
        this.putBytes (value, offset, len, blankBytes);
    }
    /**
     *
     * @param value byte[]
     * @param offset int
     * @param len int
     */
    public void putUnicodeBytes (
        byte [] value,
        int offset,
        int len)
    {
        this.putBytes (value, offset, len, blankUnicodeBytes);
    }
    /**
     *
     * @param value char[]
     * @param offsetInBytes int
     * @param lenInBytes int
     */
    public void
    putBigUnicode (
        char [] value,
        int offsetInBytes,
        int lenInBytes)
    {
        int copyLen = value.length;
        int lenInChars = lenInBytes / unicodeWidthC;
        int fillLen = 0;

        /*
         * calculate sizes
         */
        if (copyLen > lenInChars) {
            copyLen = lenInChars;
        }
        else if (copyLen <  lenInBytes) {
            fillLen = lenInBytes - (copyLen * unicodeWidthC);
        }
        /*
         * convert unicode to bytes
         */
        int realOffset = this.ptrOffs + offsetInBytes;
        for (int i = 0; i < copyLen; ++i) {
            char current = value [i];
            this.data [i * 2 + realOffset] = (byte) (current / 256);
            this.data [i * 2 + realOffset + 1] = (byte) (current % 256);
        }
        /*
         * fill with unicode blanks
         */
        if (fillLen > 0) {
            this.fill (offsetInBytes + (copyLen * unicodeWidthC),
                fillLen, blankUnicodeBytes);
        }
    }
    /**
     * Write a signed one byte integer at <i>offset</i>
     * @param value int
     * @param offset int
     */
    public void putInt1 (
        int value,
        int offset)
    {
        data [offset + this.ptrOffs] = (byte) value;
        return;
    }
    /**
     * Write a signed two byte integer at <i>offset</i>
     * @param value The value to write.
     * @param offset The offset.
     */
    public void putInt2 (int value,
                         int offset)
    {
        int ofs = offset + this.ptrOffs;
        byte[] d=data;
        
        if(value==0) {
            d[ofs]=d[ofs+1]=0;
            return;
        } 
        d[ofs] = (byte) ((value >> 8) & 0xff); // high byte
        d[ofs + 1] = (byte)(value  & 0xff);    // low byte
        return;
    }
    
    /**
     * Write a signed four byte integer at <i>offset</i>
     * @param value int
     * @param offset int
     */
    public void putInt4 (
        int value,
        int offset)
    {
        int ofs = offset + this.ptrOffs;
        byte[] d=data;
        
        if(value==0) {
            d[ofs] = d[ofs+1] = d[ofs+2] = d[ofs+3] = 0;
            return;
        }
        
        for (int i = 3; i >= 0; --i) {
            d[ofs + i] = (byte) (value & 0xff);
            value >>= 8;
        }
        return;
    }
    
	public void putInt8 (
		long value,
		int offset)
	{
		int ofs = offset + this.ptrOffs;
		byte[] d=data;
        
		if(value==0) {
			d[ofs] = d[ofs+1] = d[ofs+2] = d[ofs+3] =
			d[ofs+4] = d[ofs+5] = d[ofs+6] = d[ofs+7] 
			= 0;
			return;
		}
        
		for (int i = 7; i >= 0; --i) {
			d[ofs + i] = (byte) (value & 0xff);
			value >>= 8;
		}
		return;
	}

    /**
     *
     * @param value java.lang.String
     * @param offset int
     */
    public int putStringThrowExc (
        String value,
        int offset) throws ConversionExceptionSapDB
    {
        // encoding default is ISO8859_1
        // other encodings are not supported (now).
        
        int ofs=offset+this.ptrOffs;
        char[] ca=value.toCharArray();
        int strlen=ca.length;
        
        for(int i=0; i<strlen; ++i) {
            char c=ca[i];
            if(c <= 255 ) {
                data[ofs++]=(byte) c;
            } else {
//                data[ofs++]=63; // '?'
               throw new ConversionExceptionSapDB(MessageTranslator.translate(MessageKey.ERROR_UNICODETOASCII,value));
            }
        }
        
        return strlen;
    }
    
    /**
    *
    * @param value java.lang.String
    * @param offset int
    */
   public int putString (
       String value,
       int offset)
   {
       // encoding default is ISO8859_1
       // other encodings are not supported (now).
       
       int ofs=offset+this.ptrOffs;
       char[] ca=value.toCharArray();
       int strlen=ca.length;
       
       for(int i=0; i<strlen; ++i) {
           char c=ca[i];
           if(c <= 255 ) {
               data[ofs++]=(byte) c;
           } else {
               data[ofs++]=63; // '?'
           }
       }
       
       return strlen;
   }
    /**
     *
     * @return int
     * @param value java.lang.String
     * @param offset int
     * @param len int
     */
    public int putString (
        String value,
        int offset,
        int len)
    {
        char[] ca=value.toCharArray();
        int strlen=ca.length;
        if(strlen >= len) {
            int ofs=offset+this.ptrOffs;
            for(int i=0; i<len; ++i) {
                char c=ca[i];
                if(c <= 255 ) {
                    data[ofs + i]=(byte) c;
                } else {
                    data[ofs + i ]=63; // '?'
                }
            }
        } else {
            int ofs=offset+this.ptrOffs;
            for(int i=0; i<strlen; ++i) {
                char c=ca[i];
                if(c <= 255 ) {
                    data[ofs + i]=(byte) c;
                } else {
                    data[ofs + i ]=63; // '?'
                }
            }
            fill(offset + strlen, len - strlen, blankBytes);
        }
        return len;
    }

    /**
     *
     * @return int
     */
    public int size ()
    {
        return this.data.length - this.ptrOffs;
    }
    /**
     *
     */
    private void fill (
        int offset,
        int len,
        byte [] filler)
    {
        int chunkLen;

        while (len > 0) {
            chunkLen = java.lang.Math.min (len, fillBufSizeC);
            System.arraycopy (filler, 0, this.data, offset + this.ptrOffs, chunkLen);
            len -= chunkLen;
            offset += chunkLen;
        }
    }
    /**
     *
     * @param outstream java.io.Writer
     */
    public void traceOn (
        PrintStream outstream)
    {
        traceOn (outstream, 0, this.byteSize);
        return;
    }
    /**
     *
     * @param outstream java.io.Writer
     */
    public void traceOn (
        PrintStream outstream,
        int maxBuf)
    {
        traceOn (outstream, 0, maxBuf);
        return;
    }
    /**
     *
     * @param outstream Writer
     * @param from int
     * @param to int
     */
    public void traceOn (
        PrintStream outstream,
        int from,
        int to)
    {
        outstream.print (StringUtil.hexDump (this.data, from + this.ptrOffs, to + this.ptrOffs));
    }

    public static byte[] getInt4Const(int value)
    {
        byte[] int4bytes=new byte[4];
        for (int i = 3; i >= 0; --i) {
            byte oneByte = (byte) (value & 0xff);
            int4bytes[i] = oneByte;
            value >>= 8;
        }
        return int4bytes;
    }

	/**
	 * @param offset
	 * @return
	 */
	public long getInt8(int offset) {
		byte[] d=this.data;
		int ofs=offset+this.ptrOffs;
		long result=0;
        
		for (int i = 0; i < 8; ++i) {
			int oneByte = d[ofs + i];
			if (oneByte < 0) {
				oneByte += 256;
			}
			result = (result * 256) + oneByte;
		}
        
		return result;	
	}

}
