/* Copyright (c) 2015 Michael Freeman, All Rights Reserved
 * 
 * The contents of this file is dual-licensed under 2 
 * alternative Open Source/Free licenses: LGPL 2.1 or later and 
 * Apache License 2.0. (starting with JNA version 4.0.0).
 * 
 * You can freely decide which license you want to apply to 
 * the project.
 * 
 * You may obtain a copy of the LGPL License at:
 * 
 * http://www.gnu.org/licenses/licenses.html
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "LGPL2.1".
 * 
 * You may obtain a copy of the Apache License at:
 * 
 * http://www.apache.org/licenses/
 * 
 * A copy is also included in the downloadable source code package
 * containing JNA, in file "AL2.0".
 */
package com.sun.jna.platform.win32;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.sun.jna.Native;
import com.sun.jna.platform.win32.Wininet.INTERNET_CACHE_ENTRY_INFO;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;

/**
 * Reusable functions that use WinInet
 */
public class WininetUtil {
    /**
     * Helper function for traversing wininet's cache and returning all entries.
     * <br>
     * Some entries are cookies, some entries are history items, and some are
     * actual files.<br>
     * 
     * @return A map of cache URL => local file (or URL => empty string for
     *         cookie and history entries)
     */
    public static Map<String, String> getCache() {
        List<INTERNET_CACHE_ENTRY_INFO> items = new ArrayList<Wininet.INTERNET_CACHE_ENTRY_INFO>();

        HANDLE cacheHandle = null;
        Win32Exception we = null;
        int lastError = 0;

        // return
        Map<String, String> cacheItems = new LinkedHashMap<String, String>();

        try {
            IntByReference size = new IntByReference();

            // for every entry, we call the API twice:
            // once to get the size into the IntByReference
            // then again to get the actual item
            cacheHandle = Wininet.INSTANCE.FindFirstUrlCacheEntry(null, null, size);
            lastError = Native.getLastError();
            
            // if there's nothing in the cache, we're done.
            if (lastError == WinError.ERROR_NO_MORE_ITEMS) {
                return cacheItems;
            } else if (lastError != WinError.ERROR_SUCCESS && lastError != WinError.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(lastError);
            }

            INTERNET_CACHE_ENTRY_INFO entry = new INTERNET_CACHE_ENTRY_INFO(size.getValue());
            cacheHandle = Wininet.INSTANCE.FindFirstUrlCacheEntry(null, entry, size);

            if (cacheHandle == null) {
                throw new Win32Exception(Native.getLastError());
            }

            items.add(entry);

            while (true) {
                size = new IntByReference();

                // for every entry, we call the API twice:
                // once to get the size into the IntByReference
                // then again to get the actual item
                boolean result = Wininet.INSTANCE.FindNextUrlCacheEntry(cacheHandle, null, size);

                if (!result) {
                    lastError = Native.getLastError();
                    if (lastError == WinError.ERROR_NO_MORE_ITEMS) {
                        break;
                    } else if (lastError != WinError.ERROR_SUCCESS && lastError != WinError.ERROR_INSUFFICIENT_BUFFER) {
                        throw new Win32Exception(lastError);
                    }
                }

                entry = new INTERNET_CACHE_ENTRY_INFO(size.getValue());
                result = Wininet.INSTANCE.FindNextUrlCacheEntry(cacheHandle, entry, size);

                if (!result) {
                    lastError = Native.getLastError();
                    if (lastError == WinError.ERROR_NO_MORE_ITEMS) {
                        break;
                    } else if (lastError != WinError.ERROR_SUCCESS && lastError != WinError.ERROR_INSUFFICIENT_BUFFER) {
                        throw new Win32Exception(lastError);
                    }
                }
                items.add(entry);
            }

            for (INTERNET_CACHE_ENTRY_INFO item : items) {
                cacheItems.put(item.lpszSourceUrlName.getWideString(0), item.lpszLocalFileName == null ? "" : item.lpszLocalFileName.getWideString(0));
            }

        } catch (Win32Exception e) {
            we = e;
        } finally {
            if (cacheHandle != null) {
                if (!Wininet.INSTANCE.FindCloseUrlCache(cacheHandle)) {
                    if (we != null) {
                        Win32Exception e = new Win32Exception(Native.getLastError());
                        e.addSuppressed(we);
                        we = e;
                    }
                }
            }
        }
        if (we != null) {
            throw we;
        }
        return cacheItems;
    }
}