/**
 * Copyright 2013 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 jogamp.newt;

import com.jogamp.nativewindow.ScalableSurface;
import com.jogamp.nativewindow.util.DimensionImmutable;
import com.jogamp.nativewindow.util.Rectangle;
import com.jogamp.common.util.ArrayHashSet;
import com.jogamp.newt.MonitorDevice;
import com.jogamp.newt.MonitorMode;
import com.jogamp.newt.Screen;

public class MonitorDeviceImpl extends MonitorDevice {

    /**
     * @param screen associated {@link Screen}
     * @param nativeHandle unique monitor device long handle, implementation specific
     * @param nativeId unique monitor device integer Id, implementation specific
     * @param name optional monitor name, maybe null
     * @param isClone flag
     * @param isPrimary flag
     * @param sizeMM size in millimeters
     * @param currentMode
     * @param pixelScale pre-fetched current pixel-scale, maybe {@code null} for {@link ScalableSurface#IDENTITY_PIXELSCALE}.
     * @param viewportPU viewport in pixel-units
     * @param viewportWU viewport in window-units
     * @param supportedModes all supported {@link MonitorMode}s
     */
    public MonitorDeviceImpl(final ScreenImpl screen, final long nativeHandle, final int nativeId, final String name, final boolean isClone,
                             final boolean isPrimary, final DimensionImmutable sizeMM, final MonitorMode currentMode,
                             final float[] pixelScale, final Rectangle viewportPU, final Rectangle viewportWU, final ArrayHashSet<MonitorMode> supportedModes) {
        super(screen, nativeHandle, nativeId, name, isClone, isPrimary, sizeMM, currentMode, pixelScale, viewportPU, viewportWU, supportedModes);
    }

    @Override
    public final MonitorMode queryCurrentMode() throws IllegalStateException {
        final ScreenImpl screenImpl = (ScreenImpl)screen;
        if( !screenImpl.isNativeValid() ) {
            throw new IllegalStateException("Screen is not created natively: "+screenImpl);
        }
        final ScreenMonitorState sms = screenImpl.getScreenMonitorStatus(true);
        sms.lock();
        try {
            final MonitorMode mm0 = screenImpl.queryCurrentMonitorModeIntern(this);
            if(null == mm0) {
                throw new InternalError("getCurrentMonitorModeIntern() == null");
            }
            MonitorMode mmU = supportedModes.get(mm0); // unified instance
            if( null == mmU ) {
                // add new mode avoiding exception!
                mmU = sms.getMonitorModes().getOrAdd(mm0);
                mmU = supportedModes.getOrAdd(mmU);
                if( Screen.DEBUG ) {
                    System.err.println("Adding new mode: "+mm0+" -> "+mmU);
                }
            }
            // if mode has changed somehow, update it ..
            if( getCurrentMode().hashCode() != mmU.hashCode() ) {
                setCurrentModeValue(mmU, isPrimary);
                sms.fireMonitorModeChanged(this, mmU, true);
            }
            return mmU;
        } finally {
            sms.unlock();
        }
    }

    @Override
    public final boolean setCurrentMode(final MonitorMode mode) throws IllegalStateException {
        final ScreenImpl screenImpl = (ScreenImpl)screen;
        if( !screenImpl.isNativeValid() ) {
            throw new IllegalStateException("Screen is not created natively: "+screenImpl);
        }
        if(Screen.DEBUG) {
            System.err.println("Screen.setCurrentMode.0: "+this+" -> "+mode);
        }
        final ScreenMonitorState sms = screenImpl.getScreenMonitorStatus(true);
        sms.lock();
        try {
            final MonitorMode mmC = queryCurrentMode();
            final MonitorMode mmU = supportedModes.get(mode); // unify via value hash
            if( null == mmU ) {
                throw new IllegalArgumentException("Given mode not in set of modes. Current mode "+mode+", "+this);
            }
            if( mmU.equals( mmC ) ) {
                if(Screen.DEBUG) {
                    System.err.println("Screen.setCurrentMode: 0.0 is-current (skip) "+mmU+" == "+mmC);
                }
                return true;
            }
            final long tStart;
            if(Screen.DEBUG) {
                tStart = System.currentTimeMillis();
            } else {
                tStart = 0;
            }

            sms.fireMonitorModeChangeNotify(this, mmU);
            if(Screen.DEBUG) {
                System.err.println("Screen.setCurrentMode ("+(System.currentTimeMillis()-tStart)+"ms): fireModeChangeNotify() "+mmU);
            }

            boolean success = screenImpl.setCurrentMonitorModeImpl(this, mmU);
            if(success) {
                if(Screen.DEBUG) {
                    System.err.println("Screen.setCurrentMode ("+(System.currentTimeMillis()-tStart)+"ms): setCurrentModeImpl() "+mmU+", success(1): "+success);
                }
            } else {
                // 2nd attempt validate!
                final MonitorMode queriedCurrent = queryCurrentMode(); // may fireModeChanged(..) if successful and differs!
                success = queriedCurrent.hashCode() == mmU.hashCode() ;
                if(Screen.DEBUG) {
                    System.err.println("Screen.setCurrentMode.2: queried "+queriedCurrent);
                    System.err.println("Screen.setCurrentMode ("+(System.currentTimeMillis()-tStart)+"ms): setCurrentModeImpl() "+mmU+", success(2): "+success);
                }
            }
            if( success ) {
                setCurrentModeValue(mmU, isPrimary);
                modeChanged = !isOriginalMode();
            }
            sms.fireMonitorModeChanged(this, mmU, success);
            if(Screen.DEBUG) {
                System.err.println("Screen.setCurrentMode ("+(System.currentTimeMillis()-tStart)+"ms): X.X: success "+success+": "+this);
            }
            return success;
        } finally {
            sms.unlock();
        }
    }

    private final void setCurrentModeValue(final MonitorMode currentMode, final boolean isPrimary) {
        this.currentMode = currentMode;
        this.isPrimary = isPrimary;
    }

    /* pp */ final Rectangle getMutuableViewportPU() { return viewportPU; }
    /* pp */ final Rectangle getMutuableViewportWU() { return viewportWU; }

    /* pp */ final ArrayHashSet<MonitorMode> getSupportedModesImpl() {
        return supportedModes;
    }

    /* pp */ final void setIsClone(final boolean isClone) {
        this.isClone = isClone;
    }
    /* pp */ final void setIsPrimary(final boolean isPrimary) {
        this.isPrimary = isPrimary;
    }
}
