package org.j7zip.SevenZip.Archive.SevenZip;
import java.io.IOException;

import org.j7zip.Common.ByteBuffer;
import org.j7zip.Common.LimitedSequentialInStream;
import org.j7zip.Common.LockedInStream;
import org.j7zip.Common.LockedSequentialInStreamImp;
import org.j7zip.Common.LongVector;
import org.j7zip.Common.ObjectVector;
import org.j7zip.Common.RecordVector;
import org.j7zip.SevenZip.HRESULT;
import org.j7zip.SevenZip.ICompressCoder;
import org.j7zip.SevenZip.ICompressCoder2;
import org.j7zip.SevenZip.ICompressFilter;
import org.j7zip.SevenZip.ICompressSetDecoderProperties2;
import org.j7zip.SevenZip.Archive.Common.BindPair;
import org.j7zip.SevenZip.Archive.Common.CoderMixer2;
import org.j7zip.SevenZip.Archive.Common.CoderMixer2ST;
import org.j7zip.SevenZip.Archive.Common.CoderStreamsInfo;
import org.j7zip.SevenZip.Archive.Common.FilterCoder;



// import SevenZip.Archive.Common.CoderMixer2MT;


class Decoder {
    
    boolean _bindInfoExPrevIsDefined;
    BindInfoEx _bindInfoExPrev;
    
    boolean _multiThread;
    
    // CoderMixer2MT _mixerCoderMTSpec;
    CoderMixer2ST _mixerCoderSTSpec;
    CoderMixer2 _mixerCoderCommon;
    
    ICompressCoder2 _mixerCoder;
    ObjectVector<Object> _decoders;
    
    public Decoder(boolean multiThread) {
        _multiThread = multiThread;
        _bindInfoExPrevIsDefined = false;
        _bindInfoExPrev = new BindInfoEx();
        
        _mixerCoder = null;
        _decoders = new ObjectVector<>();
        
        // #ifndef EXCLUDE_COM -- LoadMethodMap();
    }
    
    static void ConvertFolderItemInfoToBindInfo(Folder folder,BindInfoEx bindInfo) {
        bindInfo.Clear();
        
        for (int i = 0; i < folder.BindPairs.size(); i++) {
            BindPair bindPair = new BindPair();
            bindPair.InIndex = folder.BindPairs.get(i).InIndex;
            bindPair.OutIndex = folder.BindPairs.get(i).OutIndex;
            bindInfo.BindPairs.add(bindPair);
        }
        int outStreamIndex = 0;
        for (int i = 0; i < folder.Coders.size(); i++) {
            CoderStreamsInfo coderStreamsInfo = new CoderStreamsInfo();
            CoderInfo coderInfo = folder.Coders.get(i);
            coderStreamsInfo.NumInStreams = coderInfo.NumInStreams;
            coderStreamsInfo.NumOutStreams = coderInfo.NumOutStreams;
            bindInfo.Coders.add(coderStreamsInfo);
            AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();
            bindInfo.CoderMethodIDs.add(altCoderInfo.MethodID);
            for (int j = 0; j < coderStreamsInfo.NumOutStreams; j++, outStreamIndex++)
                if (folder.FindBindPairForOutStream(outStreamIndex) < 0)
                    bindInfo.OutStreams.add(outStreamIndex);
        }
        for (int i = 0; i < folder.PackStreams.size(); i++)
            bindInfo.InStreams.add(folder.PackStreams.get(i));
    }
    static boolean AreCodersEqual(CoderStreamsInfo a1, CoderStreamsInfo a2) {
        return (a1.NumInStreams == a2.NumInStreams) &&
                (a1.NumOutStreams == a2.NumOutStreams);
    }
    
    static boolean AreBindPairsEqual(BindPair a1, BindPair a2) {
        return (a1.InIndex == a2.InIndex) &&
                (a1.OutIndex == a2.OutIndex);
    }
    
    static boolean AreBindInfoExEqual(BindInfoEx a1, BindInfoEx a2) {
        if (a1.Coders.size() != a2.Coders.size())
            return false;
        int i;
        for (i = 0; i < a1.Coders.size(); i++)
            if (!AreCodersEqual(a1.Coders.get(i), a2.Coders.get(i)))
                return false;
        if (a1.BindPairs.size() != a2.BindPairs.size())
            return false;
        for (i = 0; i < a1.BindPairs.size(); i++)
            if (!AreBindPairsEqual(a1.BindPairs.get(i), a2.BindPairs.get(i)))
                return false;
        for (i = 0; i < a1.CoderMethodIDs.size(); i++)
            if (a1.CoderMethodIDs.get(i) != a2.CoderMethodIDs.get(i))
                return false;
        if (a1.InStreams.size() != a2.InStreams.size())
            return false;
        if (a1.OutStreams.size() != a2.OutStreams.size())
            return false;
        return true;
    }
    
    int Decode(org.j7zip.SevenZip.IInStream inStream,
            long startPos,
            LongVector packSizes, int packSizesOffset, // const UInt64 *packSizes,
            Folder folderInfo,
            java.io.OutputStream outStream,
            org.j7zip.SevenZip.ICompressProgressInfo compressProgress // , // ICompressProgressInfo *compressProgress,
            // _ST_MODE boolean mtMode, int numThreads
            ) throws IOException {
        
        
        ObjectVector<java.io.InputStream> inStreams = new ObjectVector<>(); // CObjectVector< CMyComPtr<ISequentialInStream> >
        
        LockedInStream lockedInStream = new LockedInStream();
        lockedInStream.Init(inStream);
        
        for (int j = 0; j < folderInfo.PackStreams.size(); j++) {
            LockedSequentialInStreamImp lockedStreamImpSpec = new LockedSequentialInStreamImp();
            java.io.InputStream lockedStreamImp = lockedStreamImpSpec;
            lockedStreamImpSpec.Init(lockedInStream, startPos);
            startPos += packSizes.get(j+packSizesOffset);
            
            LimitedSequentialInStream streamSpec = new LimitedSequentialInStream();
            java.io.InputStream inStream2 = streamSpec;
            streamSpec.SetStream(lockedStreamImp);
            streamSpec.Init(packSizes.get(j+packSizesOffset));
            inStreams.add(inStream2);
        }
        
        int numCoders = folderInfo.Coders.size();
        
        BindInfoEx bindInfo = new BindInfoEx();
        ConvertFolderItemInfoToBindInfo(folderInfo, bindInfo);
        boolean createNewCoders;
        if (!_bindInfoExPrevIsDefined)
            createNewCoders = true;
        else
            createNewCoders = !AreBindInfoExEqual(bindInfo, _bindInfoExPrev);
        
        
        
        if (createNewCoders) {
            int i;
            _decoders.clear();
            
            if (_mixerCoder != null) _mixerCoder.close(); // _mixerCoder.Release();
            
            if (_multiThread) {
                /*
                _mixerCoderMTSpec = new CoderMixer2MT();
                _mixerCoder = _mixerCoderMTSpec;
                _mixerCoderCommon = _mixerCoderMTSpec;
                */
                throw new IOException("multithreaded decoder not implemented");
            } else {
                _mixerCoderSTSpec = new CoderMixer2ST();
                _mixerCoder = _mixerCoderSTSpec;
                _mixerCoderCommon = _mixerCoderSTSpec;
            }
            _mixerCoderCommon.SetBindInfo(bindInfo);
            
            for (i = 0; i < numCoders; i++) {
                CoderInfo coderInfo = folderInfo.Coders.get(i);
                AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();
                /*
                #ifndef EXCLUDE_COM
                        CMethodInfo methodInfo;
                if (!GetMethodInfo(altCoderInfo.MethodID, methodInfo))
                    return E_NOTIMPL;
                #endif
                 */
                
                if (coderInfo.IsSimpleCoder()) {
                    ICompressCoder decoder = null;
                    ICompressFilter filter = null;
                    
                    // #ifdef COMPRESS_LZMA
                    if (altCoderInfo.MethodID.equals(MethodID.k_LZMA))
                        decoder = new org.j7zip.SevenZip.Compression.LZMA.Decoder(); // NCompress::NLZMA::CDecoder;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_PPMD))
                        System.out.println("PPMD not implemented"); // decoder = new NCompress::NPPMD::CDecoder;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ_X86))
                        filter = new org.j7zip.SevenZip.Compression.Branch.BCJ_x86_Decoder();
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_Deflate))
                        System.out.println("DEFLATE not implemented"); // decoder = new NCompress::NDeflate::NDecoder::CCOMCoder;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_BZip2))
                        System.out.println("BZIP2 not implemented"); // decoder = new NCompress::NBZip2::CDecoder;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_Copy))
                        decoder = new org.j7zip.SevenZip.Compression.Copy.Decoder(); // decoder = new NCompress::CCopyCoder;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_7zAES))
                        throw new IOException("k_7zAES not implemented"); // filter = new NCrypto::NSevenZ::CDecoder;
                    
                    if (filter != null) {
                        FilterCoder coderSpec = new FilterCoder();
                        decoder = coderSpec;
                        coderSpec.Filter = filter;
                    }
                    /*
                        #ifndef EXCLUDE_COM
                                if (decoder == 0) {
                            RINOK(_libraries.CreateCoderSpec(methodInfo.FilePath,
                                    methodInfo.Decoder, &decoder));
                                }
                        #endif
                     */
                    if (decoder == null)
                        return HRESULT.E_NOTIMPL;
                    
                    _decoders.add(decoder);
                    
                    if (_multiThread) {
                        // _mixerCoderMTSpec.AddCoder(decoder);
                        throw new IOException("Multithreaded decoder is not implemented");
                    } else {
                        _mixerCoderSTSpec.AddCoder(decoder, false);
                    }
                } else {

                    ICompressCoder2 decoder = null;
                     
                    if (altCoderInfo.MethodID.equals(MethodID.k_BCJ2)) {
                        decoder = new org.j7zip.SevenZip.Compression.Branch.BCJ2_x86_Decoder();
                    }
                            
                     
                            if (decoder == null)
                                return HRESULT.E_NOTIMPL;
                     
                    _decoders.add(decoder);
                    if (_multiThread) {
                       // _mixerCoderMTSpec.AddCoder2(decoder);
                       throw new IOException("Multithreaded decoder is not implemented");
                    } else {
                        _mixerCoderSTSpec.AddCoder2(decoder, false);
                    }
                }
            }
            _bindInfoExPrev = bindInfo;
            _bindInfoExPrevIsDefined = true;
        }
        
        int i;
        _mixerCoderCommon.ReInit();
        
        int packStreamIndex = 0, unPackStreamIndex = 0;
        int coderIndex = 0;
        
        for (i = 0; i < numCoders; i++) {
            CoderInfo coderInfo = folderInfo.Coders.get(i);
            AltCoderInfo altCoderInfo = coderInfo.AltCoders.Front();
            Object decoder = _decoders.get(coderIndex); // CMyComPtr<IUnknown> &decoder = _decoders[coderIndex];
            
            {
                try {
                    ICompressSetDecoderProperties2 setDecoderProperties = (ICompressSetDecoderProperties2)decoder;
                
                    ByteBuffer properties = altCoderInfo.Properties;
                    int size = properties.GetCapacity();
                    if (size == -1) // (size > 0xFFFFFFFF)
                        return HRESULT.E_NOTIMPL;
                    if (size > 0) {
                        boolean ret = setDecoderProperties.SetDecoderProperties2(properties.data() /* , size */ );
                        if (ret == false) return HRESULT.E_FAIL;
                    }
                } catch (ClassCastException e) {
                    // nothing to do
                }
            }
            /*
            #ifdef COMPRESS_MT
                    if (mtMode) {
                CMyComPtr<ICompressSetCoderMt> setCoderMt;
                decoder.QueryInterface(IID_ICompressSetCoderMt, &setCoderMt);
                if (setCoderMt) {
                    RINOK(setCoderMt->SetNumberOfThreads(numThreads));
                }
                    }
            #endif
             
            #ifndef _NO_CRYPTO
             {
                CMyComPtr<ICryptoSetPassword> cryptoSetPassword;
                decoder.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword);
                if (cryptoSetPassword) {
                    if (getTextPassword == 0)
                        return E_FAIL;
                    CMyComBSTR password;
                    RINOK(getTextPassword->CryptoGetTextPassword(&password));
                    CByteBuffer buffer;
                    UString unicodePassword(password);
                    const UInt32 sizeInBytes = unicodePassword.Length() * 2;
                    buffer.SetCapacity(sizeInBytes);
                    for (int i = 0; i < unicodePassword.Length(); i++) {
                        wchar_t c = unicodePassword[i];
                        ((Byte *)buffer)[i * 2] = (Byte)c;
                        ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8);
                    }
                    RINOK(cryptoSetPassword->CryptoSetPassword(
                            (const Byte *)buffer, sizeInBytes));
                }
            }
            #endif
             */
            coderIndex++;
            
            int numInStreams = coderInfo.NumInStreams;
            int numOutStreams = coderInfo.NumOutStreams;
            LongVector packSizesPointers = new LongVector(); // CRecordVector<const UInt64 *>
            LongVector unPackSizesPointers = new LongVector(); // CRecordVector<const UInt64 *>
            packSizesPointers.Reserve(numInStreams);
            unPackSizesPointers.Reserve(numOutStreams);
            int j;
            for (j = 0; j < numOutStreams; j++, unPackStreamIndex++)
                unPackSizesPointers.add(folderInfo.UnPackSizes.get(unPackStreamIndex));
            
            for (j = 0; j < numInStreams; j++, packStreamIndex++) {
                int bindPairIndex = folderInfo.FindBindPairForInStream(packStreamIndex);
                if (bindPairIndex >= 0)
                    packSizesPointers.add(
                            folderInfo.UnPackSizes.get(folderInfo.BindPairs.get(bindPairIndex).OutIndex));
                else {
                    int index = folderInfo.FindPackStreamArrayIndex(packStreamIndex);
                    if (index < 0)
                        return HRESULT.E_FAIL;
                    packSizesPointers.add(packSizes.get(index));
                }
            }
            
            _mixerCoderCommon.SetCoderInfo(i,
                    packSizesPointers, // &packSizesPointers.Front(),
                    unPackSizesPointers // &unPackSizesPointers.Front()
                    );
        }
        
        int [] temp_useless = new int [1]; // TBD
        int [] tmp1 = new int[1];
        bindInfo.FindOutStream(bindInfo.OutStreams.get(0), tmp1 /* mainCoder */ , temp_useless /* temp */);
        
        if (_multiThread) {
           // _mixerCoderMTSpec.SetProgressCoderIndex(mainCoder);
           throw new IOException("Multithreaded decoder is not implemented");
        }
        
        if (numCoders == 0)
            return 0;
        RecordVector<java.io.InputStream> inStreamPointers = new RecordVector<>(); // CRecordVector<ISequentialInStream *>
        inStreamPointers.Reserve(inStreams.size());
        for (i = 0; i < inStreams.size(); i++)
            inStreamPointers.add(inStreams.get(i));

        RecordVector<java.io.OutputStream> outStreamPointer = new RecordVector<>();
        outStreamPointer.add(outStream);
        return _mixerCoder.Code(
                inStreamPointers, //&inStreamPointers.Front(),
                null,
                inStreams.size(),
                outStreamPointer, // &outStreamPointer,
                null, 1, compressProgress);
    }
}