/*
 * Copyright (c) 2000-2011 TeamDev Ltd. All rights reserved.
 * TeamDev PROPRIETARY and CONFIDENTIAL.
 * Use is subject to license terms.
 */
package com.jniwrapper.win32.system;

import com.jniwrapper.*;
import com.jniwrapper.win32.FunctionName;
import com.jniwrapper.win32.LastErrorException;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * This class provides the ability to retrieve version and other information from an executable files.
 *
 * @author Serge Piletsky
 */
public class FileInformation {
    private static final FunctionName GetFileVersionInfo = new FunctionName("GetFileVersionInfo");
    private static final FunctionName GetFileVersionInfoSize = new FunctionName("GetFileVersionInfoSize");
    private static final FunctionName VerQueryValue = new FunctionName("VerQueryValue");

    private Pointer dataBufferPtr = new Pointer(null, true);
    private VS_FIXEDFILEINFO fixedfileinfo = new VS_FIXEDFILEINFO();
    private Library version;

    public FileInformation(File file) throws IOException {
        if (file == null) {
            throw new IllegalArgumentException();
        }
        if (!file.exists()) {
            throw new FileNotFoundException("File not found: " + file);
        }

        String fileName = file.getAbsolutePath();

        version = new Library("version");

        Function getFileVersionInfoSize = version.getFunction(GetFileVersionInfoSize.toString());
        UInt32 fileInfoSize = new UInt32();
        getFileVersionInfoSize.invoke(fileInfoSize, new Str(fileName), new Pointer.Void());

        Function getFileVersionInfo = version.getFunction(GetFileVersionInfo.toString());

        Bool result = new Bool();
        PrimitiveArray dataBuffer = new PrimitiveArray(UInt8.class, (int) fileInfoSize.getValue());
        dataBufferPtr = new Pointer(dataBuffer);

        long lastErrorCode = getFileVersionInfo.invoke(result,
                new Str(fileName),
                new UInt32(),
                fileInfoSize,
                dataBufferPtr);
        if (!result.getValue()) {
            throw new LastErrorException(lastErrorCode);
        }

        Function verQueryValue = version.getFunction(VerQueryValue.toString());
        fixedfileinfo = new VS_FIXEDFILEINFO();
        UInt bufferSize = new UInt();
        lastErrorCode = verQueryValue.invoke(result,
                dataBufferPtr,
                new Str("\\"),
                new Pointer(new Pointer(fixedfileinfo)),
                new Pointer(bufferSize));

        if (!result.getValue()) {
            throw new LastErrorException(lastErrorCode);
        }
    }

    /**
     * Returns the major file version.
     *
     * @return major file version
     */
    public int getMajorVersion() {
        return (int) getHighWord(fixedfileinfo.FileVersionMS.getValue());
    }

    /**
     * Returns the minor file version.
     *
     * @return minor file version
     */
    public int getMinorVersion() {
        return (int) getLoWord(fixedfileinfo.FileVersionMS.getValue());
    }

    /**
     * Returns file build number
     * @return file build number
     */
    public int getBuild(){
        return (int) getHighWord(fixedfileinfo.FileVersionLS.getValue());
    }

    /**
     * Returns file revision number
     * @return file revision number
     */
    public int getRevision(){
        return (int) getLoWord(fixedfileinfo.FileVersionLS.getValue());
    }

    private static long getHighWord(long value) {
        return (value & 0xFFFF0000) >> 16;
    }

    private static long getLoWord(long value) {
        return value & 0xFFFF;
    }

    /**
     * Returns major product version.
     *
     * @return major version
     */
    public int getProductMajorVersion() {
        return (int) getHighWord(fixedfileinfo.ProductVersionMS.getValue());
    }

    /**
     * Returns minor product version.
     *
     * @return minor version
     */
    public int getProductMinorVersion() {
        return (int) getLoWord(fixedfileinfo.ProductVersionMS.getValue());
    }

    /**
     * Returns product build number.
     *
     * @return build number
     */
    public int getProductBuildNumber() {
        return (int) getHighWord(fixedfileinfo.ProductVersionLS.getValue());
    }

    /**
     * Returns product revision number.
     *
     * @return revision number
     */
    public int getProductRevisionNumber() {
        return (int) getLoWord(fixedfileinfo.ProductVersionLS.getValue());
    }

    /**
     * Returns the company name.
     *
     * @return company name
     */
    public String getCompanyName() {
        Function verQueryValue = version.getFunction(VerQueryValue.toString());
        Bool result = new Bool();
        UInt32 langInfo = new UInt32();

        long lastErrorCode = verQueryValue.invoke(result,
                dataBufferPtr,
                new Str("\\VarFileInfo\\Translation"),
                new Pointer(new Pointer(langInfo)),
                new Pointer(new UInt())
        );
        if (!result.getValue()) {
            throw new LastErrorException(lastErrorCode);
        }

        StringBuffer verStrName = new StringBuffer("\\StringFileInfo\\");
        long language = getLoWord(langInfo.getValue());
        String langStr = Long.toHexString(language);
        verStrName.append("0").append(langStr.toUpperCase());
        long codePage = getHighWord(langInfo.getValue());
        String codePageStr = Long.toHexString(codePage);
        verStrName.append("0").append(codePageStr.toUpperCase());
        verStrName.append("\\CompanyName");

        Str companyName = new Str();
        lastErrorCode = verQueryValue.invoke(result,
                dataBufferPtr,
                new Str(verStrName.toString()),
                new Pointer(new Pointer(companyName)),
                new UInt());
        if (!result.getValue()) {
            throw new LastErrorException(lastErrorCode);
        }

        return companyName.getValue();
    }

    /**
     * Wrapper for <code>VS_FIXEDFILEINFO</code> structure.
     */
    private static class VS_FIXEDFILEINFO extends Structure {
        UInt32 Signature = new UInt32();
        UInt32 StrucVersion = new UInt32();
        UInt32 FileVersionMS = new UInt32();
        UInt32 FileVersionLS = new UInt32();
        UInt32 ProductVersionMS = new UInt32();
        UInt32 ProductVersionLS = new UInt32();
        UInt32 FileFlagsMask = new UInt32();
        UInt32 FileFlags = new UInt32();
        UInt32 FileOS = new UInt32();
        UInt32 FileType = new UInt32();
        UInt32 FileSubtype = new UInt32();
        UInt32 FileDateMS = new UInt32();
        UInt32 FileDateLS = new UInt32();

        public VS_FIXEDFILEINFO() {
            init(new Parameter[]{
                    Signature,
                    StrucVersion,
                    FileVersionMS,
                    FileVersionLS,
                    ProductVersionMS,
                    ProductVersionLS,
                    FileFlagsMask,
                    FileFlags,
                    FileOS,
                    FileType,
                    FileSubtype,
                    FileDateMS,
                    FileDateLS,
            }, (short) 8);
        }

        public Object clone() {
            VS_FIXEDFILEINFO clone = new VS_FIXEDFILEINFO();
            clone.initFrom(this);
            return clone;
        }
    }

    public static void main(String[] args) throws Exception {
        File file = new File("c:\\Program Files\\Internet Explorer\\iexplore.exe");
//        File file = new File("c:\\Windows\\System32\\comctl32.dll");
        FileInformation fileInformation = new FileInformation(file);
        System.out.println("fileInformation.getMajorVersion() = " + fileInformation.getMajorVersion());
        System.out.println("fileInformation.getMinorVersion() = " + fileInformation.getMinorVersion());
        System.out.println("fileInformation.getBuild() = " + fileInformation.getBuild());
        System.out.println("fileInformation.getRevision() = " + fileInformation.getRevision());
        System.out.println("fileInformation.getCompanyName() = " + fileInformation.getCompanyName());

        System.out.println("fileInformation.getProductMajorVersion() = " + fileInformation.getProductMajorVersion());
        System.out.println("fileInformation.getProductMinorVersion() = " + fileInformation.getProductMinorVersion());
        System.out.println("fileInformation.getProductBuildNumber() = " + fileInformation.getProductBuildNumber());
        System.out.println("fileInformation.getProductRevisionNumber() = " + fileInformation.getProductRevisionNumber());
    }
}