
package org.j7zip.SevenZip.Compression.Branch;

import org.j7zip.Common.RecordVector;
import org.j7zip.SevenZip.HRESULT;
import org.j7zip.SevenZip.ICompressCoder2;
import org.j7zip.SevenZip.ICompressProgressInfo;
import org.j7zip.SevenZip.Common.InBuffer;
import org.j7zip.SevenZip.Compression.LZ.OutWindow;


public class BCJ2_x86_Decoder implements ICompressCoder2 {

    public static final int kNumMoveBits = 5;
    
    InBuffer _mainInStream = new InBuffer();
    InBuffer _callStream = new InBuffer();
    InBuffer _jumpStream = new InBuffer();
    
    org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder _statusE8Decoder[] = new org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder[256];
    org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder _statusE9Decoder = new org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);
    org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder _statusJccDecoder = new org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);
    
    OutWindow _outStream = new OutWindow();
    org.j7zip.SevenZip.Compression.RangeCoder.Decoder _rangeDecoder = new org.j7zip.SevenZip.Compression.RangeCoder.Decoder();
    
    
    // static final boolean IsJcc(int b0, int b1) {
    //     return ((b0 == 0x0F) && ((b1 & 0xF0) == 0x80));
    // }
    
    int CodeReal(
            RecordVector<java.io.InputStream>  inStreams,
            Object useless1, // const UInt64 ** /* inSizes */,
            int numInStreams,
            RecordVector<java.io.OutputStream> outStreams,
            Object useless2, // const UInt64 ** /* outSizes */,
            int numOutStreams,
            ICompressProgressInfo progress) throws java.io.IOException {
        
        if (numInStreams != 4 || numOutStreams != 1)
            return HRESULT.E_INVALIDARG;
        
        _mainInStream.Create(1 << 16);
        _callStream.Create(1 << 20);
        _jumpStream.Create(1 << 16);
        _rangeDecoder.Create(1 << 20);
        _outStream.Create(1 << 16);
        
        _mainInStream.SetStream(inStreams.get(0));
        _callStream.SetStream(inStreams.get(1));
        _jumpStream.SetStream(inStreams.get(2));
        _rangeDecoder.SetStream(inStreams.get(3));
        _outStream.SetStream(outStreams.get(0));
        
        _mainInStream.Init();
        _callStream.Init();
        _jumpStream.Init();
        _rangeDecoder.Init();
        _outStream.Init();
        
        for (int i = 0; i < 256; i++) {
            _statusE8Decoder[i] = new org.j7zip.SevenZip.Compression.RangeCoder.BitDecoder(kNumMoveBits);
            _statusE8Decoder[i].Init();
        }
        _statusE9Decoder.Init();
        _statusJccDecoder.Init();
        
        int prevByte = 0;
        int processedBytes = 0;
        for (;;) {
            
            if (processedBytes > (1 << 20) && progress != null) {
                long nowPos64 = _outStream.GetProcessedSize();
                int res = progress.SetRatioInfo(ICompressProgressInfo.INVALID, nowPos64);
                if (res != HRESULT.S_OK) return res;
                
                processedBytes = 0;
            }
            
            processedBytes++;
            int b = _mainInStream.read();
            if (b == -1)
                return Flush();
            _outStream.WriteByte(b); // System.out.println("0:"+b);
            // if ((b != 0xE8) && (b != 0xE9) && (!IsJcc(prevByte, b))) {
            if ((b != 0xE8) && (b != 0xE9) && (!((prevByte == 0x0F) && ((b & 0xF0) == 0x80)))) {
                prevByte = b;
                continue;
            }
            
            boolean status;
            if (b == 0xE8)
                status = (_statusE8Decoder[prevByte].Decode(_rangeDecoder) == 1);
            else if (b == 0xE9)
                status = (_statusE9Decoder.Decode(_rangeDecoder) == 1);
            else
                status = (_statusJccDecoder.Decode(_rangeDecoder) == 1);
            
            if (status) {
                int src;
                if (b == 0xE8) {
                    int b0 = _callStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src = (b0) << 24;
                    
                    b0 = _callStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0) << 16;
                    
                    b0 = _callStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0) << 8;
                    
                    b0 = _callStream.read();
                    if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0);
                    
                } else {
                    int b0 = _jumpStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src = (b0) << 24;
                    
                    b0 = _jumpStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0) << 16;
                    
                    b0 = _jumpStream.read();
                    // if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0) << 8;
                    
                    b0 = _jumpStream.read();
                    if(b0 == -1) return HRESULT.S_FALSE;
                    src |= (b0);
                    
                }
                int dest = src - ((int)_outStream.GetProcessedSize() + 4) ;
                _outStream.WriteByte(dest);
                _outStream.WriteByte((dest >> 8));
                _outStream.WriteByte((dest >> 16));
                _outStream.WriteByte((dest >> 24));
                prevByte = dest >> 24 & 0xFF;
                processedBytes += 4;
            } else
                prevByte = b;
        }
    }
    
    public int Flush() throws java.io.IOException {
        _outStream.Flush();
        return HRESULT.S_OK;
    }
    
    public int Code(
            RecordVector<java.io.InputStream>  inStreams, // ISequentialInStream **inStreams,
            Object useless_inSizes, // const UInt64 ** /* inSizes */,
            int numInStreams,
            RecordVector<java.io.OutputStream> outStreams, // ISequentialOutStream **outStreams
            Object useless_outSizes, // const UInt64 ** /* outSizes */,
            int numOutStreams,
            ICompressProgressInfo progress) throws java.io.IOException {
        
        try {
            return CodeReal(inStreams, useless_inSizes, numInStreams,
                    outStreams, useless_outSizes,numOutStreams, progress);
        } catch(java.io.IOException e) {
            throw e;
        } finally {
            ReleaseStreams();
        }
    }
    
    void ReleaseStreams() throws java.io.IOException {
        _mainInStream.ReleaseStream();
        _callStream.ReleaseStream();
        _jumpStream.ReleaseStream();
        _rangeDecoder.ReleaseStream();
        _outStream.ReleaseStream();
    }
    
    public void close() throws java.io.IOException {
        ReleaseStreams();       
    }
    
}
