/*
 * Copyright 2007 nori090
 *
 * 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 jimmy;

import static jimmy.Checksum.sum16;
import static jimmy.ConvertUtil.littleEndianBytes2int;
import static jimmy.ConvertUtil.toLittleEndianBytes;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;

import jimmy.util._ByteArrayInputStream;

import org.apache.commons.codec.binary.Hex;

/**
 * winny query
 * 
 * @author nori090
 * @version $Rev: 17 $ $Date: 2008-03-25 22:45:24 +0900 (Tue, 25 Mar 2008) $
 */
public class Query {
    // メンバ
    String sharing_address = "";

    int sharing_port = 0;

    String bbs_address = "";

    int bbs_port = 0;

    int file_size = 0;

    byte[] hash = null;

    String file_name = "";

    String trip = "";

    byte[] bbs_trip = null;

    int timer = 0;

    int block_size = 0;

    int modified_time = 0;

    boolean ignore = false;

    int version = 0;

    public Query unpack( _ByteArrayInputStream in )
        throws WinnyProtocolException {
        int header_length = ( ( 4 + 4 ) * 2 ) + 4 + 16 + 1;
        if ( in.available() < header_length ) {
            throw new WinnyProtocolException( "query header size error" );
        }
        try {
            sharing_address = InetAddress.getByAddress( in.read( 4 ) ).getHostAddress();
            sharing_port = littleEndianBytes2int( in.read( 2 ) );
            bbs_address = InetAddress.getByAddress( in.read( 4 ) ).getHostAddress();
            bbs_port = littleEndianBytes2int( in.read( 2 ) );
            file_size = littleEndianBytes2int( in.read( 4 ) );
            hash = in.read( 16 );
            int file_name_length = littleEndianBytes2int( in.read( 1 ) );
            if ( in.available() < file_name_length ) {
                throw new WinnyProtocolException( "ready byte size error" );
            }
            byte[] checksum = in.read( 2 );
            // checksum is 2byte and little endian
            checksum = new byte[] { checksum[1], checksum[0] };
            file_name = this.unpackFileName( checksum, in.read( file_name_length ) );
            if ( in.available() < 12 ) {
                throw new WinnyProtocolException( "ready byte size error" );
            }
            trip = String.valueOf( Hex.encodeHex( ConvertUtil.getCStringByte( in.read( 11 ) ) ) );
            int bbs_trip_length = littleEndianBytes2int( in.read( 1 ) );
            if ( in.available() < bbs_trip_length ) {
                throw new WinnyProtocolException( "ready byte size error" );
            }
            bbs_trip = in.read( bbs_trip_length );
            if ( in.available() < 12 ) {
                throw new WinnyProtocolException( "ready byte size error" );
            }
            timer = littleEndianBytes2int( in.read( 2 ) );
            block_size = littleEndianBytes2int( in.read( 4 ) );
            modified_time = littleEndianBytes2int( in.read( 4 ) );
            ignore = littleEndianBytes2int( in.read( 1 ) ) == 1 ? true : false;
            version = littleEndianBytes2int( in.read( 1 ) );
            // 意味内けどclose
            in.close();
        }
        catch ( IOException e ) {
            throw new WinnyProtocolException( "query unpack error", e );
        }
        return this;

    }

    public Query unpack( byte[] byte_query )
        throws WinnyProtocolException {
        return unpack( new _ByteArrayInputStream( byte_query ) );
    }

    String unpackFileName( byte[] checksum, byte[] bs )
        throws WinnyProtocolException {
        try {
            byte[] unpack = RC4.decrypt( checksum, bs );
            short short_checksum = (short) ( ( ( checksum[0] & 0xFF ) << 8 ) | ( checksum[1] & 0xFF ) );
            short unpack_sum = sum16( unpack );
            if ( short_checksum != unpack_sum ) {
                throw new WinnyProtocolException();
            }
            String fname = new String( unpack, "windows-31j" );
            return fname;
        }
        catch ( Exception e ) {
            e.printStackTrace();
            throw new WinnyProtocolException( e );
        }
    }

    public byte[] pack()
        throws WinnyProtocolException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            out.write( InetAddress.getByName( sharing_address ).getAddress() );
            out.write( toLittleEndianBytes( sharing_port ), 0, 2 );
            out.write( InetAddress.getByName( bbs_address ).getAddress() );
            out.write( toLittleEndianBytes( bbs_port ), 0, 2 );
            out.write( toLittleEndianBytes( file_size ) );
            out.write( hash );
            byte[] b_fname = file_name.getBytes( "windows-31j" );
            out.write( b_fname.length );
            byte[] checksum = toLittleEndianBytes( sum16( b_fname ) );
            out.write( checksum );
            out.write( RC4.encrypt( new byte[] { checksum[0] }, b_fname ) );

            String padding_trip;
            if ( trip.length() != ( 11 * 2 ) ) {
                StringBuilder sb = new StringBuilder( trip );
                for ( int c = ( ( ( 11 * 2 ) - trip.length() ) / 2 ) + 1; c < 11; c++ ) {
                    sb.append( "00" );
                }
                padding_trip = sb.toString();
            }
            else {
                padding_trip = trip;
            }
            out.write( Hex.decodeHex( padding_trip.toCharArray() ) );
            out.write( bbs_trip.length );
            out.write( bbs_trip );
            out.write( toLittleEndianBytes( (short) timer ) );
            out.write( toLittleEndianBytes( block_size ) );
            out.write( toLittleEndianBytes( modified_time ) );
            out.write( ignore ? 1 : 0 );
            out.write( version );
            return out.toByteArray();
        }
        catch ( Exception e ) {
            throw new WinnyProtocolException( e );
        }
        finally {
            try {
                // ByteArrayOutputStreamは意味内けど
                out.close();
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    }

    public String getSharing_address() {
        return sharing_address;
    }

    public void setSharing_address( String sharing_address ) {
        this.sharing_address = sharing_address;
    }

    public int getSharing_port() {
        return sharing_port;
    }

    public void setSharing_port( int sharing_port ) {
        this.sharing_port = sharing_port;
    }

    public String getBbs_address() {
        return bbs_address;
    }

    public void setBbs_address( String bbs_address ) {
        this.bbs_address = bbs_address;
    }

    public int getBbs_port() {
        return bbs_port;
    }

    public void setBbs_port( int bbs_port ) {
        this.bbs_port = bbs_port;
    }

    public int getFile_size() {
        return file_size;
    }

    public void setFile_size( int file_size ) {
        this.file_size = file_size;
    }

    public byte[] getHash() {
        return hash;
    }

    public void setHash( byte[] hash ) {
        this.hash = hash;
    }

    public String getFile_name() {
        return file_name;
    }

    public void setFile_name( String file_name ) {
        this.file_name = file_name;
    }

    public String getTrip() {
        return trip;
    }

    public void setTrip( String trip ) {
        this.trip = trip;
    }

    public byte[] getBbs_trip() {
        return bbs_trip;
    }

    public void setBbs_trip( byte[] bbs_trip ) {
        this.bbs_trip = bbs_trip;
    }

    public int getTimer() {
        return timer;
    }

    public void setTimer( int timer ) {
        this.timer = timer;
    }

    public int getBlock_size() {
        return block_size;
    }

    public void setBlock_size( int block_size ) {
        this.block_size = block_size;
    }

    public int getModified_time() {
        return modified_time;
    }

    public void setModified_time( int modified_time ) {
        this.modified_time = modified_time;
    }

    public boolean isIgnore() {
        return ignore;
    }

    public void setIgnore( boolean ignore ) {
        this.ignore = ignore;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion( int version ) {
        this.version = version;
    }
    
}
