/**
 * Copyright 2012 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */
package com.jogamp.opengl.test.junit.util;

import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;

import org.junit.Assert;

import com.jogamp.common.util.IntIntHashMap;
import com.jogamp.newt.event.KeyEvent;

public class NEWTKeyUtil {
    public static final int TIME_OUT     = 2000; // 2s
    public static final int POLL_DIVIDER   = 20; // TO/20
    public static final int TIME_SLICE   = TIME_OUT / POLL_DIVIDER ;

    public static class CodeSeg {
        public final short min;
        public final short max;
        public final String description;

        public CodeSeg(int min, int max, String description) {
            this.min = (short)min;
            this.max = (short)max;
            this.description = description;
        }
    }
    public static class CodeEvent {
        public final short code;
        public final String description;
        public final KeyEvent event;

        public CodeEvent(short code, String description, KeyEvent event) {
            this.code = code;
            this.description = description;
            this.event = event;
        }
        public String toString() {
            return "Code 0x"+Integer.toHexString( code & 0x0000FFFF )+" != "+event+" // "+description;
        }
    }

    public static void dumpKeyEvents(List<EventObject> keyEvents) {
        for(int i=0; i<keyEvents.size(); i++) {
            System.err.println(i+": "+keyEvents.get(i));
        }
    }

    public static boolean validateKeyCodes(CodeSeg[] codeSegments, List<List<EventObject>> keyEventsList, boolean verbose) {
        final List<CodeEvent> missCodes = new ArrayList<CodeEvent>();
        int totalCodeCount = 0;
        boolean res = true;
        for(int i=0; i<codeSegments.length; i++) {
            final CodeSeg codeSeg = codeSegments[i];
            totalCodeCount += codeSeg.max - codeSeg.min + 1;
            final List<EventObject> keyEvents = keyEventsList.get(i);
            res &= validateKeyCodes(missCodes, codeSeg, keyEvents, verbose);
        }
        if(verbose) {
            System.err.println("*** Total KeyCode Misses "+missCodes.size()+" / "+totalCodeCount+", valid "+res);
            for(int i=0; i<missCodes.size(); i++) {
                System.err.println("Miss["+i+"]: "+missCodes.get(i));
            }
        }
        return res;
    }
    public static boolean validateKeyCodes(List<CodeEvent> missCodes, CodeSeg codeSeg, List<EventObject> keyEvents, boolean verbose) {
        final int codeCount = codeSeg.max - codeSeg.min + 1;
        int misses = 0;
        int evtIdx = 0;
        for(int i=0; i<codeCount; i++) {
            // evtIdx -> KEY_PRESSED !
            final short c = (short) ( codeSeg.min + i );
            final KeyEvent e = (KeyEvent) ( evtIdx < keyEvents.size() ? keyEvents.get(evtIdx) : null );
            if( null == e ) {
                missCodes.add(new CodeEvent(c, codeSeg.description, e));
                misses++;
                evtIdx++;
            } else {
                if( c != e.getKeyCode() ) {
                    missCodes.add(new CodeEvent(c, codeSeg.description, e));
                    misses++;
                }
                evtIdx += 2;
            }
        }
        final boolean res = evtIdx == keyEvents.size() && 0 == missCodes.size();
        if(verbose) {
            System.err.println("+++ Code Segment "+codeSeg.description+", Misses: "+misses+" / "+codeCount+", events "+keyEvents.size()+", valid "+res);
        }
        return res;
    }

    public static void validateKeyEvent(KeyEvent e, short eventType, int modifiers, short keyCode, char keyChar) {
        if(0 <= eventType) {
            Assert.assertTrue("KeyEvent type mismatch, expected 0x"+Integer.toHexString(eventType)+", has "+e, eventType == e.getEventType());
        }
        if(0 <= modifiers) {
            Assert.assertTrue("KeyEvent modifier mismatch, expected 0x"+Integer.toHexString(modifiers)+", has "+e, modifiers == e.getModifiers());
        }
        if(KeyEvent.VK_UNDEFINED !=  keyCode) {
            Assert.assertTrue("KeyEvent code mismatch, expected 0x"+Integer.toHexString(keyCode)+", has "+e, keyCode == e.getKeyCode());
        }
        if(KeyEvent.NULL_CHAR != keyChar) {
            Assert.assertTrue("KeyEvent char mismatch, expected 0x"+Integer.toHexString(keyChar)+", has "+e, keyChar == e.getKeyChar());
        }
    }

    public static short getNextKeyEventType(KeyEvent e) {
        final int et = e.getEventType();
        switch( et ) {
            case KeyEvent.EVENT_KEY_PRESSED:
                return KeyEvent.EVENT_KEY_RELEASED;
            case KeyEvent.EVENT_KEY_RELEASED:
                return KeyEvent.EVENT_KEY_PRESSED;
            default:
                Assert.assertTrue("Invalid event "+e, false);
                return 0;
        }
    }

    public static void validateKeyEventOrder(List<EventObject> keyEvents) {
        IntIntHashMap keyCode2NextEvent = new IntIntHashMap();
        for(int i=0; i<keyEvents.size(); i++) {
            final KeyEvent e = (KeyEvent) keyEvents.get(i);
            int eet = keyCode2NextEvent.get(e.getKeyCode());
            if( 0 >= eet ) {
                eet = KeyEvent.EVENT_KEY_PRESSED;
            }
            final int et = e.getEventType();
            Assert.assertEquals("Key event not in proper order "+i+"/"+keyEvents.size()+" - event "+e, eet, et);
            eet = getNextKeyEventType(e);
            keyCode2NextEvent.put(e.getKeyCode(), eet);
        }
    }

    /**
     * @param keyAdapter
     * @param expPressedCountSI number of single key press events
     * @param expReleasedCountSI number of single key release events
     * @param expPressedCountAR number of auto-repeat key press events
     * @param expReleasedCountAR number of auto-repeat key release events
     */
    public static void validateKeyAdapterStats(NEWTKeyAdapter keyAdapter,
                                               int expPressedCountSI, int expReleasedCountSI,
                                               int expPressedCountAR, int expReleasedCountAR) {
        final int expPressReleaseCountSI = expPressedCountSI + expReleasedCountSI;
        final int expPressReleaseCountAR = expPressedCountAR + expReleasedCountAR;
        final int expPressReleaseCountALL = expPressReleaseCountSI + expPressReleaseCountAR;

        final int keyPressedALL = keyAdapter.getKeyPressedCount(false);
        final int keyPressedAR = keyAdapter.getKeyPressedCount(true);
        final int keyReleasedALL = keyAdapter.getKeyReleasedCount(false);
        final int keyReleasedAR = keyAdapter.getKeyReleasedCount(true);

        final int keyPressedSI = keyPressedALL-keyPressedAR;
        final int keyReleasedSI = keyReleasedALL-keyReleasedAR;

        final int pressReleaseCountALL = keyPressedALL + keyReleasedALL;
        final int pressReleaseCountSI = keyPressedSI + keyReleasedSI;
        final int pressReleaseCountAR = keyPressedAR + keyReleasedAR;

        System.err.println("Expec Single Press "+expPressedCountSI +", Release "+expReleasedCountSI);
        System.err.println("Expec AutoRp Press "+expPressedCountAR +", Release "+expReleasedCountAR);

        System.err.println("Total Single Press "+keyPressedSI   +", Release "+keyReleasedSI   +", Events "+pressReleaseCountSI);
        System.err.println("Total AutoRp Press "+keyPressedAR   +", Release "+keyReleasedAR   +", Events "+pressReleaseCountAR);
        System.err.println("Total ALL    Press "+keyPressedALL  +", Release "+keyReleasedALL  +", Events "+pressReleaseCountALL);

        Assert.assertEquals("Internal Error: pressReleaseSI != pressReleaseALL - pressReleaseAR", pressReleaseCountSI, pressReleaseCountALL - pressReleaseCountAR);

        Assert.assertEquals("Key press count failure (SI)", expPressedCountSI, keyPressedSI);
        Assert.assertEquals("Key released count failure (SI)", expReleasedCountSI, keyReleasedSI);

        Assert.assertEquals("Key press count failure (AR)", expPressedCountAR, keyPressedAR);
        Assert.assertEquals("Key released count failure (AR)", expReleasedCountAR, keyReleasedAR);

        Assert.assertEquals("Key pressRelease count failure (SI)", expPressReleaseCountSI, pressReleaseCountSI);
        Assert.assertEquals("Key pressRelease count failure (AR)", expPressReleaseCountAR, pressReleaseCountAR);

        final List<EventObject> keyEvents = keyAdapter.copyQueue();

        Assert.assertEquals("Key pressRelease count failure (ALL) w/ list sum  ", expPressReleaseCountALL, pressReleaseCountALL);
        Assert.assertEquals("Key total count failure (ALL) w/ list size ", pressReleaseCountALL, keyEvents.size());
    }
}
