/* jcifs smb client library in Java
 * Copyright (C) 2003  "Michael B. Allen" <jcifs at samba dot org>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package jcifs.smb;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import jcifs.util.Encdec;

public class SmbRandomAccessFile implements DataOutput, DataInput {

    private static final int WRITE_OPTIONS = 0x0842;

    private SmbFile file;
    private long fp;
    private int openFlags, access = 0, readSize, writeSize, ch, options = 0;
    private byte[] tmp = new byte[8];
    private SmbComWriteAndXResponse write_andx_resp = null;

    public SmbRandomAccessFile( String url, String mode, int shareAccess )
            throws SmbException, MalformedURLException, UnknownHostException {
        this( new SmbFile( url, "", null, shareAccess ), mode );
    }
    public SmbRandomAccessFile( SmbFile file, String mode )
            throws SmbException, MalformedURLException, UnknownHostException {
        this.file = file;
        if( mode.equals( "r" )) {
            this.openFlags = SmbFile.O_CREAT | SmbFile.O_RDONLY;
        } else if( mode.equals( "rw" )) {
            this.openFlags = SmbFile.O_CREAT | SmbFile.O_RDWR | SmbFile.O_APPEND;
            write_andx_resp = new SmbComWriteAndXResponse();
            options = WRITE_OPTIONS;
            access = SmbConstants.FILE_READ_DATA | SmbConstants.FILE_WRITE_DATA;
        } else {
            throw new IllegalArgumentException( "Invalid mode" );
        }
        file.open( openFlags, access, SmbFile.ATTR_NORMAL, options );

        boolean isSigningEnabled = (file.tree.session.transport.flags2 & ServerMessageBlock.FLAGS2_SECURITY_SIGNATURES) == ServerMessageBlock.FLAGS2_SECURITY_SIGNATURES;

      if(!isSigningEnabled && (file.tree.session.transport.server.capabilities & SmbConstants.CAP_LARGE_READX) == SmbConstants.CAP_LARGE_READX) {
            readSize = Math.min(SmbConstants.RCV_BUF_SIZE - 70, 0xFFFF -70);
        } else {
            readSize = file.tree.session.transport.rcv_buf_size - 70;
        }
        
        if(!isSigningEnabled && (file.tree.session.transport.server.capabilities & SmbConstants.CAP_LARGE_WRITEX) == SmbConstants.CAP_LARGE_WRITEX) {
            writeSize = Math.min(SmbConstants.SND_BUF_SIZE - 70, 0xFFFF - 70);
        } else {
            writeSize = Math.min( file.tree.session.transport.snd_buf_size - 70,
                                  file.tree.session.transport.server.maxBufferSize - 70 );
        }
        fp = 0L;
    }

    public int read() throws SmbException {
        if( read( tmp, 0, 1 ) == -1 ) {
            return -1;
        }
        return tmp[0] & 0xFF;
    }
    public int read( byte b[] ) throws SmbException {
        return read( b, 0, b.length );
    }
    public int read( byte b[], int off, int len ) throws SmbException {
        if( len <= 0 ) {
            return 0;
        }
        long start = fp;

        // ensure file is open
        if( file.isOpen() == false ) {
            file.open( openFlags, 0, SmbFile.ATTR_NORMAL, options );
        }

        int r, n;
        SmbComReadAndXResponse response = new SmbComReadAndXResponse( b, off );
        do {
            r = len > readSize ? readSize : len;
            file.send( new SmbComReadAndX( file.fid, fp, r, null ), response );
            if(( n = response.dataLength ) <= 0 ) {
                return (int)((fp - start) > 0L ? fp - start : -1);
            }
            fp += n;
            len -= n;
            response.off += n;
        } while( len > 0 && n == r );

        return (int)(fp - start);
    }
    public final void readFully( byte b[] ) throws SmbException {
        readFully( b, 0, b.length );
    }
    public final void readFully( byte b[], int off, int len ) throws SmbException {
        int n = 0, count;

        do {    
            count = this.read( b, off + n, len - n );
            if( count < 0 ) throw new SmbException( "EOF" );
            n += count;
            fp += count;
        } while( n < len );
    }
    public int skipBytes( int n ) throws SmbException {
        if (n > 0) {
            fp += n;
            return n;
        }
        return 0;
    }

    public void write( int b ) throws SmbException {
        tmp[0] = (byte)b;
        write( tmp, 0, 1 );
    }
    public void write( byte b[] ) throws SmbException {
        write( b, 0, b.length );
    }
    public void write( byte b[], int off, int len ) throws SmbException {
        if( len <= 0 ) {
            return;
        }

        // ensure file is open
        if( file.isOpen() == false ) {
            file.open( openFlags, 0, SmbFile.ATTR_NORMAL, options );
        }

        int w;
        do {
            w = len > writeSize ? writeSize : len;
            file.send( new SmbComWriteAndX( file.fid, fp, len - w, b, off, w, null ), write_andx_resp );
            fp += write_andx_resp.count;
            len -= write_andx_resp.count;
            off += write_andx_resp.count;
        } while( len > 0 );
    }
    public long getFilePointer() throws SmbException {
        return fp;
    }
    public void seek( long pos ) throws SmbException {
        fp = pos;
    }
    public long length() throws SmbException {
        return file.length();
    }
    public void setLength( long newLength ) throws SmbException {
        // ensure file is open
        if( file.isOpen() == false ) {
            file.open( openFlags, 0, SmbFile.ATTR_NORMAL, options );
        }
        SmbComWriteResponse rsp = new SmbComWriteResponse();
        file.send( new SmbComWrite( file.fid, (int)(newLength & 0xFFFFFFFFL), 0, tmp, 0, 0 ), rsp );
    }
    public void close() throws SmbException {
        file.close();
    }

    public final boolean readBoolean() throws SmbException {
        if((read( tmp, 0, 1 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return tmp[0] != (byte)0x00;
    }
    public final byte readByte() throws SmbException {
        if((read( tmp, 0, 1 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return tmp[0];
    }
    public final int readUnsignedByte() throws SmbException {
        if((read( tmp, 0, 1 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return tmp[0] & 0xFF;
    }
    public final short readShort() throws SmbException {
        if((read( tmp, 0, 2 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_uint16be( tmp, 0 );
    }
    public final int readUnsignedShort() throws SmbException {
        if((read( tmp, 0, 2 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_uint16be( tmp, 0 ) & 0xFFFF;
    }
    public final char readChar() throws SmbException {
        if((read( tmp, 0, 2 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return (char)Encdec.dec_uint16be( tmp, 0 );
    }
    public final int readInt() throws SmbException {
        if((read( tmp, 0, 4 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_uint32be( tmp, 0 );
    }
    public final long readLong() throws SmbException {
        if((read( tmp, 0, 8 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_uint64be( tmp, 0 );
    }
    public final float readFloat() throws SmbException {
        if((read( tmp, 0, 4 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_floatbe( tmp, 0 );
    }
    public final double readDouble() throws SmbException {
        if((read( tmp, 0, 8 )) < 0 ) {
            throw new SmbException( "EOF" );
        }
        return Encdec.dec_doublebe( tmp, 0 );
    }
    public final String readLine() throws SmbException {
        StringBuffer input = new StringBuffer();
        int c = -1;
        boolean eol = false;

        while (!eol) {
            switch( c = read() ) {
                case -1:
                case '\n':
                    eol = true;
                    break;
                case '\r':
                    eol = true;
                    long cur = fp;
                    if( read() != '\n' ) {
                        fp = cur;
                    }
                    break;
                default:
                    input.append( (char)c );
                    break;
            }
        }

        if ((c == -1) && (input.length() == 0)) {
            return null;
        }

        return input.toString();
    }

    public final String readUTF() throws SmbException {
        int size = readUnsignedShort();
        byte[] b = new byte[size];
        read( b, 0, size );
        try {
            return Encdec.dec_utf8( b, 0, size );
        } catch( IOException ioe ) {
            throw new SmbException( "", ioe );
        }
    }
    public final void writeBoolean( boolean v ) throws SmbException {
        tmp[0] = (byte)(v ? 1 : 0);
        write( tmp, 0, 1 );
    }
    public final void writeByte( int v ) throws SmbException {
        tmp[0] = (byte)v;
        write( tmp, 0, 1 );
    }
    public final void writeShort( int v ) throws SmbException {
        Encdec.enc_uint16be( (short)v, tmp, 0 );
        write( tmp, 0, 2 );
    }
    public final void writeChar( int v ) throws SmbException {
        Encdec.enc_uint16be( (short)v, tmp, 0 );
        write( tmp, 0, 2 );
    }
    public final void writeInt( int v ) throws SmbException {
        Encdec.enc_uint32be( v, tmp, 0 );
        write( tmp, 0, 4 );
    }
    public final void writeLong( long v ) throws SmbException {
        Encdec.enc_uint64be( v, tmp, 0 );
        write( tmp, 0, 8 );
    }
    public final void writeFloat( float v ) throws SmbException {
        Encdec.enc_floatbe( v, tmp, 0 );
        write( tmp, 0, 4 );
    }
    public final void writeDouble( double v ) throws SmbException {
        Encdec.enc_doublebe( v, tmp, 0 );
        write( tmp, 0, 8 );
    }
    public final void writeBytes( String s ) throws SmbException {
        byte[] b = s.getBytes();
        write( b, 0, b.length );
    }
    public final void writeChars( String s ) throws SmbException {
        int clen = s.length();
        int blen = 2 * clen;
        byte[] b = new byte[blen];
        char[] c = new char[clen];
        s.getChars( 0, clen, c, 0 );
        for( int i = 0, j = 0; i < clen; i++ ) {
            b[j++] = (byte)(c[i] >>> 8);
            b[j++] = (byte)(c[i] >>> 0);
        }
        write( b, 0, blen );
    }
    public final void writeUTF( String str ) throws SmbException {
        int len = str.length();
        int ch, size = 0;
        byte[] dst;

        for( int i = 0; i < len; i++ ) {
            ch = str.charAt( i );
            size += ch > 0x07F ? (ch > 0x7FF ? 3 : 2) : 1;
        }
        dst = new byte[size];
        writeShort( size );
        try {
            Encdec.enc_utf8( str, dst, 0, size );
        } catch( IOException ioe ) {
            throw new SmbException( "", ioe );
        }
        write( dst, 0, size );
    }
}

