package jogamp.opengl.egl;

import javax.media.nativewindow.AbstractGraphicsConfiguration;
import javax.media.nativewindow.AbstractGraphicsDevice;
import javax.media.nativewindow.AbstractGraphicsScreen;
import javax.media.nativewindow.DefaultGraphicsScreen;
import javax.media.nativewindow.NativeSurface;
import javax.media.nativewindow.ProxySurface;
import javax.media.nativewindow.UpstreamSurfaceHook;
import javax.media.nativewindow.VisualIDHolder.VIDType;
import javax.media.opengl.GLCapabilitiesImmutable;
import javax.media.opengl.GLException;

import com.jogamp.nativewindow.egl.EGLGraphicsDevice;

public class EGLUpstreamSurfaceHook implements UpstreamSurfaceHook.MutableSize {
    protected static final boolean DEBUG = EGLDrawableFactory.DEBUG;
    private final NativeSurface upstreamSurface;
    private final UpstreamSurfaceHook.MutableSize upstreamSurfaceHookMutableSize; 
    
    public EGLUpstreamSurfaceHook(NativeSurface upstream) {
        upstreamSurface = upstream;
        if(upstreamSurface instanceof ProxySurface) {
            final UpstreamSurfaceHook ush = ((ProxySurface)upstreamSurface).getUpstreamSurfaceHook();
            if(ush instanceof UpstreamSurfaceHook.MutableSize) {
                // offscreen NativeSurface w/ MutableSize (default)
                upstreamSurfaceHookMutableSize = (UpstreamSurfaceHook.MutableSize) ush;
            } else {
                upstreamSurfaceHookMutableSize = null;
            }
        } else {
            upstreamSurfaceHookMutableSize = null;
        }
    }
    
    public final NativeSurface getUpstreamSurface() { return upstreamSurface; }
    
    static String getThreadName() { return Thread.currentThread().getName(); }
    
    public final void setSize(int width, int height) {
        if(null != upstreamSurfaceHookMutableSize) {
            upstreamSurfaceHookMutableSize.setSize(width, height);
        }        
    }
    
    @Override
    public final void create(ProxySurface surface) {
        final String dbgPrefix;
        if(DEBUG) {
            dbgPrefix = getThreadName() + ": EGLUpstreamSurfaceHook.create("+surface.getClass().getSimpleName()+"): "; 
            System.err.println(dbgPrefix+this);            
        } else {
            dbgPrefix = null;
        }
        
        if(upstreamSurface instanceof ProxySurface) {
            // propagate createNotify(..) so upstreamSurface will be created 
            ((ProxySurface)upstreamSurface).createNotify();
        }
        
        // lock upstreamSurface, so it can be used in case EGLDisplay is derived from it!
        if(NativeSurface.LOCK_SURFACE_NOT_READY >= upstreamSurface.lockSurface()) {
            throw new GLException("Could not lock: "+upstreamSurface);
        }
        try {
            evalUpstreamSurface(dbgPrefix, surface);
        } finally {
            upstreamSurface.unlockSurface();
        }        
    }
    
    private final void evalUpstreamSurface(String dbgPrefix, ProxySurface surface) {
        //
        // evaluate nature of upstreamSurface, may create EGL instances if required
        //
        
        boolean isEGLSurfaceValid = true; // assume yes
        
        final AbstractGraphicsConfiguration aConfig = upstreamSurface.getGraphicsConfiguration();        
        final AbstractGraphicsDevice aDevice = aConfig.getScreen().getDevice();
        
        final EGLGraphicsDevice eglDevice;
        if( aDevice instanceof EGLGraphicsDevice ) {
            eglDevice = (EGLGraphicsDevice) aDevice;
            if(DEBUG) {
                System.err.println(dbgPrefix+"Reusing eglDevice: "+eglDevice);
            }
            if(EGL.EGL_NO_DISPLAY == eglDevice.getHandle()) {
                eglDevice.open();
                isEGLSurfaceValid = false;
                surface.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );
            }
        } else {
            eglDevice = EGLDisplayUtil.eglCreateEGLGraphicsDevice(upstreamSurface);
            isEGLSurfaceValid = false;
            surface.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_DEVICE );                
        }
        
        final GLCapabilitiesImmutable capsRequested = (GLCapabilitiesImmutable) aConfig.getRequestedCapabilities();
        final EGLGraphicsConfiguration eglConfig;
        if( aConfig instanceof EGLGraphicsConfiguration ) {
            // Config is already in EGL type - reuse ..
            final EGLGLCapabilities capsChosen = (EGLGLCapabilities) aConfig.getChosenCapabilities();
            if( !isEGLSurfaceValid || !EGLGraphicsConfiguration.isEGLConfigValid(eglDevice.getHandle(), capsChosen.getEGLConfig()) ) { 
                // 'refresh' the native EGLConfig handle
                capsChosen.setEGLConfig(EGLGraphicsConfiguration.EGLConfigId2EGLConfig(eglDevice.getHandle(), capsChosen.getEGLConfigID()));
                if( 0 == capsChosen.getEGLConfig() ) {
                    throw new GLException("Refreshing native EGLConfig handle failed with error "+EGLContext.toHexString(EGL.eglGetError())+": "+eglDevice+", "+capsChosen+" of "+aConfig);
                }
                final AbstractGraphicsScreen eglScreen = new DefaultGraphicsScreen(eglDevice, aConfig.getScreen().getIndex());
                eglConfig  = new EGLGraphicsConfiguration(eglScreen, capsChosen, capsRequested, null);
                if(DEBUG) {
                    System.err.println(dbgPrefix+"Refreshing eglConfig: "+eglConfig);
                }
                isEGLSurfaceValid = false;
            } else {
                eglConfig = (EGLGraphicsConfiguration) aConfig;
                if(DEBUG) {
                    System.err.println(dbgPrefix+"Reusing eglConfig: "+eglConfig);
                }
            }
        } else {
            final AbstractGraphicsScreen eglScreen = new DefaultGraphicsScreen(eglDevice, aConfig.getScreen().getIndex());
            eglConfig = EGLGraphicsConfigurationFactory.chooseGraphicsConfigurationStatic(
                    capsRequested, capsRequested, null, eglScreen, aConfig.getVisualID(VIDType.NATIVE), false);

            if (null == eglConfig) {
                throw new GLException("Couldn't create EGLGraphicsConfiguration from "+eglScreen);
            } else if(DEBUG) {
                System.err.println(dbgPrefix+"Chosen eglConfig: "+eglConfig);
            }
            isEGLSurfaceValid = false;
        }
        surface.setGraphicsConfiguration(eglConfig);
        
        if(isEGLSurfaceValid) {
            isEGLSurfaceValid = EGLDrawable.isValidEGLSurface(eglDevice.getHandle(), upstreamSurface.getSurfaceHandle());
        }
        if(isEGLSurfaceValid) {
            surface.setSurfaceHandle(upstreamSurface.getSurfaceHandle());
            surface.clearUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_SURFACE );
            if(DEBUG) {
                System.err.println(dbgPrefix+"Fin: Already valid EGL surface - use as-is: "+upstreamSurface);
            }
        } else {
            surface.setSurfaceHandle(EGL.EGL_NO_SURFACE);
            surface.addUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_SURFACE ); // create/destroy in EGLDrawable
            if(DEBUG) {
                System.err.println(dbgPrefix+"Fin: EGL surface n/a - TBD: "+upstreamSurface);
            }
        }        
    }

    @Override
    public final void destroy(ProxySurface surface) {
        if(EGLDrawableFactory.DEBUG) {
            System.err.println("EGLUpstreamSurfaceHook.destroy("+surface.getClass().getSimpleName()+"): "+this);            
        }
        surface.clearUpstreamOptionBits( ProxySurface.OPT_PROXY_OWNS_UPSTREAM_SURFACE );            
        if(upstreamSurface instanceof ProxySurface) {
            ((ProxySurface)upstreamSurface).destroyNotify();
        }
    }

    @Override
    public final int getWidth(ProxySurface s) {
        return upstreamSurface.getWidth();
    }

    @Override
    public final int getHeight(ProxySurface s) {
        return upstreamSurface.getHeight();
    }
    
    @Override
    public String toString() {
        final String us_s = null != upstreamSurface ? ( upstreamSurface.getClass().getName() + ": 0x" + Long.toHexString(upstreamSurface.getSurfaceHandle()) ) : "nil";
        return "EGLUpstreamSurfaceHook[ "+ upstreamSurface.getWidth() + "x" + upstreamSurface.getHeight() + ", " + us_s+ "]";
    }

}