/*
 * [Pentium.java]
 *
 * Summary: Access Pentium/AMD specific features, e.g. RDTSC, CPUSERNO.
 *
 * Copyright: (c) 2005-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.8+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.1 2005-07-30 ANT build, consistent naming.
 *  1.2 2006-03-06 reformat with IntelliJ, add Javadoc.
 *  1.3 2007-06-03 add pad and icon
 *  1.4 2008-09-18 add cpuSerNo64 cpuSerNo96 display methods for serial number.
 *  1.5 2008-09-23 convert to jDK 1.5, deal with C++ missing DLLs.
 *  1.6 2012-12-11 add 64-bit support.
 */
package com.mindprod.pentium;

import com.mindprod.common18.ST;

import static java.lang.System.*;

/**
 * Access Pentium/AMD specific features, e.g. RDTSC, CPUSERNO.
 * <p/>
 * Uses JNI native C++/ASM code that only works on Windows.
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.6 2012-12-11 add 64-bit support.
 * @since 2005
 */
public final class Pentium
    {
    private static final int FIRST_COPYRIGHT_YEAR = 2005;

    /**
     * undisplayed copyright notice
     *
     * @noinspection UnusedDeclaration
     */
    private static final String EMBEDDED_COPYRIGHT =
            "Copyright: (c) 2005-2017 Roedy Green, Canadian Mind Products, http://mindprod.com";

    private static final String RELEASE_DATE = "2012-12-11";

    /**
     * embedded version string.
     */
    private static final String VERSION_STRING = "1.6";

    /**
     * display a 64 bit serial number in hex in 4 groups of 4 hex digits
     *
     * @param serno serial number to display
     *
     * @return hex string equivalent.
     */
    private static String display64BitSerialNumber( long serno )
        {
        final int ms = ( int ) ( serno >>> 32 );
        final int ls = ( int ) serno;
        final String mss = ST.toLZHexString( ms, 8 );
        final String lss = ST.toLZHexString( ls, 8 );
        final StringBuilder sb = new StringBuilder( 19 );
        sb.append( mss.substring( 0, 4 ) );
        sb.append( '-' );
        sb.append( mss.substring( 4, 8 ) );
        sb.append( '-' );
        sb.append( lss.substring( 0, 4 ) );
        sb.append( '-' );
        sb.append( lss.substring( 4, 8 ) );
        return sb.toString().toUpperCase();
        }

    /**
     * display a 96 bit serial number in hex in 6 groups of 4 hex digits
     *
     * @param serno serial number to display, three 32 bit numbers msb in serno[0]
     *
     * @return hex string equivalent.
     */
    private static String display96BitSerialNumber( int[] serno )
        {
        final StringBuilder sb = new StringBuilder( 30 );
        for ( int i = 0; i < 3; i++ )
            {
            final String hex = ST.toLZHexString( serno[ i ], 8 );
            sb.append( hex.substring( 0, 4 ) );
            sb.append( '-' );
            sb.append( hex.substring( 4, 8 ) );
            sb.append( '-' );
            }
        sb.setLength( 29 );
        return sb.toString().toUpperCase();
        }

    /**
     * Get the brand/model of the CPU.
     *
     * @return brand, e.g. "AMD Athlon(TM) XP 2000+"
     */
    public static native String cpuIdBrand();

    /**
     * Get the instruction set family of the CPU.
     *
     * @return family, possibly null if not Pentium.
     */
    public static native int cpuIdFamily();

    /**
     * Get the model of the CPU.
     *
     * @return model, possibly 0 if not Pentium.
     */
    public static native int cpuIdModel();

    /**
     * Get the stepping ID of the CPU.
     *
     * @return stepping ID.
     */
    public static native int cpuIdStep();

    /**
     * Get the vendor ID of the CPU.
     *
     * @return vendor, "GenuineIntel" for Intel chips, "AuthenticAMD" for AMD.
     */
    public static native String cpuIdVendor();

    /**
     * Get the 64-bit cpu Serial number for Pentium
     *
     * @return cpu serial number, possibly 0 if not Pentium.
     */
    public static native long cpuSerNo64();

    /**
     * Get the 96-bit cpu serial number for Xeon.
     *
     * @param select 0 for most significant 32 bits, 1 for middle, 2 for least significant.
     *
     * @return family, possibly 0 if not Xeon.
     */
    public static native int cpuSerNo96( int select );

    /**
     * test harness
     *
     * @param args not used
     */
    public static void main( String[] args )
        {
        // This needs to be done only once. It could go in a static init block.
        // For debugging it is better to put it here.
        // Get DLL loaded from somewhere on java.library path.
        // get DLL loaded from somewhere on java.library path.
        String dll = "pentium.x";
        try
            {
            // load pentium.32.dlll or pentium.64.dll on path
            // arch is x86 or amd64
            if ( System.getProperty( "os.arch" ).equals( "amd64" ) )
                {
                dll = "pentium.64";
                }
            else
                {
                dll = "pentium.32";
                }
            // without .dll suffix
            System.loadLibrary( dll );
            // if have troubles change this code to use load(
            // "E:\\com\\mindprod\\pentium\\pentium.32.dll" );
            }
        catch ( Exception e )
            {
            out.println( "Unable to load " + dll );
            out.println( e.getMessage() );
            System.exit( 1 );
            }
        // if have troubles change this code to use load(
        // "E:\\com\\mindprod\\pentium\\nativepentium.dll" );
        out.println( "cpuIdVendor:" + Pentium.cpuIdVendor() );
        out.println( "cpuIdBrand:" + Pentium.cpuIdBrand() );
        out.println( "cpuIdStep:" + ST.toLZHexString( Pentium.cpuIdStep(), 2 ) );
        out.println( "cpuIdModel:" + ST.toLZHexString( Pentium.cpuIdModel(), 2 ) );
        out.println( "cpuIdFamily:" + ST.toLZHexString( Pentium.cpuIdModel(), 2 ) );
        out.println( "cpuSerNo64:" + display64BitSerialNumber( Pentium.cpuSerNo64() ) );
        // collect Xeon serial number in three pieces.
        final int[] serno = new int[ 3 ];
        serno[ 0 ] = Pentium.cpuSerNo96( 0 );
        serno[ 1 ] = Pentium.cpuSerNo96( 1 );
        serno[ 2 ] = Pentium.cpuSerNo96( 2 );
        out.println( "cpuSerNo96:" + display96BitSerialNumber( serno ) );
        out.println( "rdtsc:" + Long.toHexString( Pentium.rdtsc() ) );
        }

    /**
     * Get the time stamp counter register with the RDTSC instruction giving the count of machine cycles since last
     * boot. This is very high resolution timer, with frequency matching the cpu clock crystal, not any defined time
     * unit.
     *
     * @return RDTSC Timestamp counter.
     */
    public static native long rdtsc();
    }