package org.j7zip.SevenZip.Archive.SevenZip;

import java.io.IOException;

import org.j7zip.Common.ObjectVector;
import org.j7zip.SevenZip.HRESULT;
import org.j7zip.SevenZip.ICompressProgressInfo;
import org.j7zip.SevenZip.IInStream;
import org.j7zip.SevenZip.Archive.IArchiveExtractCallback;
import org.j7zip.SevenZip.Archive.IInArchive;
import org.j7zip.SevenZip.Archive.SevenZipEntry;
import org.j7zip.SevenZip.Common.LocalCompressProgressInfo;
import org.j7zip.SevenZip.Common.LocalProgress;

public class Handler implements org.j7zip.SevenZip.Archive.IInArchive {
    
    // useless LongVector _fileInfoPopIDs = new LongVector();
    
    IInStream _inStream;
    
    ArchiveDatabaseEx _database;
    
    public Handler() {
        _database = new ArchiveDatabaseEx();
    }
    
    public int Open(IInStream stream) throws IOException {
        return Open(stream,kMaxCheckStartPosition);
    }
    
    
    public int Open(
            IInStream stream, // InStream *stream
            long maxCheckStartPosition // const UInt64 *maxCheckStartPosition,
            // IArchiveOpenCallback *openArchiveCallback */
            ) throws IOException {
        close();
        
        // useless _fileInfoPopIDs.clear();
        // TBD try
        {
            // TBD CMyComPtr<IArchiveOpenCallback> openArchiveCallbackTemp = openArchiveCallback;
            
            /* TBD
            CMyComPtr<ICryptoGetTextPassword> getTextPassword;
            if (openArchiveCallback)
            {
              openArchiveCallbackTemp.QueryInterface(
                  IID_ICryptoGetTextPassword, &getTextPassword);
            }
             */
            
            InArchive archive = new InArchive();
            int ret = archive.Open(stream, maxCheckStartPosition);
            if (ret != HRESULT.S_OK) return ret;
            ret = archive.ReadDatabase(_database); // getTextPassword
            if (ret != HRESULT.S_OK) return ret;
            _database.Fill();
            _inStream = stream;
        }
        
        // FillPopIDs(); // useless _fileInfoPopIDs
        
        return HRESULT.S_OK;
    }
    
    public int Extract(int [] indices, int numItems,
            int testModeSpec, IArchiveExtractCallback extractCallbackSpec) throws IOException {
        
        boolean testMode = (testModeSpec != 0);
        IArchiveExtractCallback extractCallback = extractCallbackSpec;
        long importantTotalUnPacked = 0;
        
        boolean allFilesMode = (numItems == -1);
        if (allFilesMode)
            numItems =
                    // #ifdef _7Z_VOL
                    // _refs.Size();
                    // #else
                    _database.Files.size();
        // #endif
        
        if(numItems == 0)
            return HRESULT.S_OK;
        
        ObjectVector<ExtractFolderInfo> extractFolderInfoVector = new ObjectVector<>();
        for(int ii = 0; ii < numItems; ii++) {
            int ref2Index = allFilesMode ? ii : indices[ii];
            
            {
        /*
      #ifdef _7Z_VOL
      // const CRef &ref = ref2.Refs[ri];
      const CRef &ref = _refs[ref2Index];
         
      int volumeIndex = ref.VolumeIndex;
      const CVolume &volume = _volumes[volumeIndex];
      const CArchiveDatabaseEx &database = volume.Database;
      UInt32 fileIndex = ref.ItemIndex;
      #else
         */
                ArchiveDatabaseEx database = _database;
                int fileIndex = ref2Index;
                //#endif
                
                int folderIndex = database.FileIndexToFolderIndexMap.get(fileIndex);
                if (folderIndex == org.j7zip.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex) {
                    extractFolderInfoVector.add( new ExtractFolderInfo(
                            // #ifdef _7Z_VOL
                            // volumeIndex,
                            // #endif
                            fileIndex, org.j7zip.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex));
                    continue;
                }
                if (extractFolderInfoVector.isEmpty() ||
                        folderIndex != extractFolderInfoVector.Back().FolderIndex
        /*
        #ifdef _7Z_VOL
        || volumeIndex != extractFolderInfoVector.Back().VolumeIndex
        #endif
         */
                        ) {
                    extractFolderInfoVector.add( new ExtractFolderInfo(
            /*
            #ifdef _7Z_VOL
            volumeIndex,
            #endif
             */
                            org.j7zip.SevenZip.Archive.SevenZip.InArchive.kNumNoIndex, folderIndex));
                    Folder folderInfo = database.Folders.get(folderIndex);
                    long unPackSize = folderInfo.GetUnPackSize();
                    importantTotalUnPacked += unPackSize;
                    extractFolderInfoVector.Back().UnPackSize = unPackSize;
                }
                
                ExtractFolderInfo efi = extractFolderInfoVector.Back();
                
                // const CFolderInfo &folderInfo = m_dam_Folders[folderIndex];
                int startIndex = database.FolderStartFileIndex.get(folderIndex); // CNum
                for (int index = efi.ExtractStatuses.size();
                index <= fileIndex - startIndex; index++) {
                    // UInt64 unPackSize = _database.Files[startIndex + index].UnPackSize;
                    // Count partial_folder_size
                    // efi.UnPackSize += unPackSize;
                    // importantTotalUnPacked += unPackSize;
                    efi.ExtractStatuses.add(index == fileIndex - startIndex);
                }
            }
        }
        
        extractCallback.SetTotal(importantTotalUnPacked);
        
        Decoder decoder = new Decoder(
                // #ifdef _ST_MODE
                false
                // #else
                // true
                // #endif
                );
        
        long currentImportantTotalUnPacked = 0;
        long totalFolderUnPacked;
        
        for(int i = 0; i < extractFolderInfoVector.size(); i++,
                currentImportantTotalUnPacked += totalFolderUnPacked) {
            ExtractFolderInfo efi = extractFolderInfoVector.get(i);
            totalFolderUnPacked = efi.UnPackSize;
            
            int res = extractCallback.SetCompleted(currentImportantTotalUnPacked);
            if (res != HRESULT.S_OK) return res;
            
            FolderOutStream folderOutStream = new FolderOutStream();
            java.io.OutputStream outStream = folderOutStream;
            
            // #ifdef _7Z_VOL
            // const CVolume &volume = _volumes[efi.VolumeIndex];
            // const CArchiveDatabaseEx &database = volume.Database;
            // #else
            ArchiveDatabaseEx database = _database;
            //#endif
            
            int startIndex; // CNum
            if (efi.FileIndex != InArchive.kNumNoIndex)
                startIndex = efi.FileIndex;
            else
                startIndex = database.FolderStartFileIndex.get(efi.FolderIndex);
            
            
            int result = folderOutStream.Init(database,
                    // #ifdef _7Z_VOL
                    // volume.StartRef2Index,
                    // #else
                    0,
                    // #endif
                    startIndex,
                    efi.ExtractStatuses, extractCallback, testMode);
            
            if (result != HRESULT.S_OK) return result;
            
            if (efi.FileIndex != InArchive.kNumNoIndex)
                continue;
            
            int folderIndex = efi.FolderIndex; // CNum
            Folder folderInfo = database.Folders.get(folderIndex);
            
            LocalProgress localProgressSpec = new LocalProgress();
            ICompressProgressInfo progress = localProgressSpec;
            localProgressSpec.Init(extractCallback, false);
            
            LocalCompressProgressInfo localCompressProgressSpec =
                    new LocalCompressProgressInfo();
            ICompressProgressInfo compressProgress = localCompressProgressSpec;
            localCompressProgressSpec.Init(progress, ICompressProgressInfo.INVALID , currentImportantTotalUnPacked);
            
            int packStreamIndex = database.FolderStartPackStreamIndex.get(folderIndex); // CNum
            long folderStartPackPos = database.GetFolderStreamPos(folderIndex, 0);
            
    /*
    #ifndef _NO_CRYPTO
    CMyComPtr<ICryptoGetTextPassword> getTextPassword;
    if (extractCallback)
      extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword);
    #endif
     */
            
            try {
                result = decoder.Decode(
                        // #ifdef _7Z_VOL
                        // volume.Stream,
                        // #else
                        _inStream,
                        // #endif
                        folderStartPackPos,
                        database.PackSizes,  // database.PackSizes.get(packStreamIndex),
                        packStreamIndex,
                        folderInfo,
                        outStream,
                        compressProgress
                        // #ifndef _NO_CRYPTO
                        // , getTextPassword
                        // #endif
                        // #ifdef COMPRESS_MT
                        // , true, _numThreads
                        // #endif
                        );
                
                if (result == HRESULT.S_FALSE) {
                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);
                    if (result != HRESULT.S_OK) return result;
                    continue;
                }
                if (result == HRESULT.E_NOTIMPL) {
                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kUnSupportedMethod);
                    if (result != HRESULT.S_OK) return result;
                    continue;
                }
                if (result != HRESULT.S_OK)
                    return result;
                if (folderOutStream.WasWritingFinished() != HRESULT.S_OK) {
                    result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);
                    if (result != HRESULT.S_OK) return result;
                    continue;
                }
            } catch(Exception e) {
                System.out.println("IOException : " + e);
                e.printStackTrace();
                result = folderOutStream.FlushCorrupted(IInArchive.NExtract_NOperationResult_kDataError);
                if (result != HRESULT.S_OK) return result;
                continue;
            }
        }
        return HRESULT.S_OK;
    }
    
    
    public int close() throws IOException {
/*
        #ifdef _7Z_VOL
        _volumes.Clear();
        _refs.Clear();
        #else
        _inStream.Release();
        _database.Clear();
        #endif
        return S_OK;
 */
        if (_inStream != null) _inStream.close();  // _inStream.Release();
        _inStream = null;
        _database.Clear();
        return 0;
    }
    /*
    public int size() {
        return _database.Files.size();
    }*/
    
    long getPackSize(int index2) {
        long packSize = 0;
        int folderIndex = _database.FileIndexToFolderIndexMap.get(index2);
        if (folderIndex != InArchive.kNumNoIndex) {
            if (_database.FolderStartFileIndex.get(folderIndex) == index2)
                packSize = _database.GetFolderFullPackSize(folderIndex);
        }
        return packSize;
    }
    
    static int GetUInt32FromMemLE(byte [] p , int off) {
        return p[off] | ((p[off + 1]) << 8) | ((p[off + 2]) << 16) | ((p[off +3]) << 24);
    }
    
    static String GetStringForSizeValue(int value) {
        for (int i = 31; i >= 0; i--)
            if ((1 << i) == value)
                return "" + i;
        String result = "";
        if (value % (1 << 20) == 0) {
            result += "" + (value >> 20);
            result += "m";
        } else if (value % (1 << 10) == 0) {
            result += "" + (value >> 10);
            result += "k";
        } else {
            result += "" + (value);
            result += "b";
        }
        return result;
    }
    
    String getMethods(int index2) {
        String ret = "";
        
        int folderIndex = _database.FileIndexToFolderIndexMap.get(index2);
        if (folderIndex != InArchive.kNumNoIndex) {
            Folder folderInfo = _database.Folders.get(folderIndex);
            String methodsString = "";
            for (int i = folderInfo.Coders.size() - 1; i >= 0; i--) {
                CoderInfo coderInfo = folderInfo.Coders.get(i);
                if (methodsString != "")
                    methodsString += ' ';
                
                // MethodInfo methodInfo;
                
                boolean methodIsKnown;
                
                for (int j = 0; j < coderInfo.AltCoders.size(); j++) {
                    if (j > 0)
                        methodsString += "|";
                    AltCoderInfo altCoderInfo = coderInfo.AltCoders.get(j);
                    
                    String methodName = "";
                    
                    methodIsKnown = true;
                    
                    if (altCoderInfo.MethodID.equals(MethodID.k_Copy))
                        methodName = "Copy";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_LZMA))
                        methodName = "LZMA";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_BCJ))
                        methodName = "BCJ";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_BCJ2))
                        methodName = "BCJ2";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_PPMD))
                        methodName = "PPMD";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_Deflate))
                        methodName = "Deflate";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_Deflate64))
                        methodName = "Deflate64";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_BZip2))
                        methodName = "BZip2";
                    else if (altCoderInfo.MethodID.equals(MethodID.k_7zAES))
                        methodName = "7zAES";
                    else
                        methodIsKnown = false;
                    
                    if (methodIsKnown) {
                        methodsString += methodName;
                        
                        if (altCoderInfo.MethodID.equals(MethodID.k_LZMA)) {
                            if (altCoderInfo.Properties.GetCapacity() >= 5) {
                                methodsString += ":";
                                int dicSize = GetUInt32FromMemLE(altCoderInfo.Properties.data(),1);
                                methodsString += GetStringForSizeValue(dicSize);
                            }
                        }
                        /* else if (altCoderInfo.MethodID == k_PPMD) {
                            if (altCoderInfo.Properties.GetCapacity() >= 5) {
                                Byte order = *(const Byte *)altCoderInfo.Properties;
                                methodsString += ":o";
                                methodsString += ConvertUInt32ToString(order);
                                methodsString += ":mem";
                                UInt32 dicSize = GetUInt32FromMemLE(
                                        ((const Byte *)altCoderInfo.Properties + 1));
                                methodsString += GetStringForSizeValue(dicSize);
                            }
                        } else if (altCoderInfo.MethodID == k_AES) {
                            if (altCoderInfo.Properties.GetCapacity() >= 1) {
                                methodsString += ":";
                                const Byte *data = (const Byte *)altCoderInfo.Properties;
                                Byte firstByte = *data++;
                                UInt32 numCyclesPower = firstByte & 0x3F;
                                methodsString += ConvertUInt32ToString(numCyclesPower);
                            }
                        } else {
                            if (altCoderInfo.Properties.GetCapacity() > 0) {
                                methodsString += ":[";
                                for (size_t bi = 0; bi < altCoderInfo.Properties.GetCapacity(); bi++) {
                                    if (bi > 5 && bi + 1 < altCoderInfo.Properties.GetCapacity()) {
                                        methodsString += "..";
                                        break;
                                    } else
                                        methodsString += GetHex2(altCoderInfo.Properties[bi]);
                                }
                                methodsString += "]";
                            }
                        }
                         */
                    } else {
                        // TBD methodsString += altCoderInfo.MethodID.ConvertToString();
                    }
                }
            }
            ret = methodsString;
        }
        
        return ret;
    }
    
    public SevenZipEntry getEntry(int index) {
        org.j7zip.SevenZip.Archive.SevenZip.FileItem item = _database.Files.get(index);
        int index2 = index;
        
        long crc = -1;
        if (item.IsFileCRCDefined) {
            crc = item.FileCRC & 0xFFFFFFFFL;
        }
        
        long position = -1;
        if (item.IsStartPosDefined)
            position = item.StartPos;
        
        SevenZipEntry entry = new SevenZipEntry(
                item.name,
                getPackSize(index2),
                item.UnPackSize,
                crc,
                item.LastWriteTime,
                position,
                item.IsDirectory,
                item.Attributes,
                getMethods(index2)
                );
        
        return entry;
    }
    
}