/*
 * Copyright (c) 2010-2023 JogAmp Community. All rights reserved.
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution 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.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 *
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */

package com.jogamp.nativewindow;

/** Specifies a set of capabilities that a window's rendering context
    must support, such as color depth per channel. It currently
    contains the minimal number of routines which allow configuration
    on all supported window systems. */
public class Capabilities implements CapabilitiesImmutable, Cloneable {
  protected final static String na_str = "----" ;

  private int     redBits        = 8;
  private int     greenBits      = 8;
  private int     blueBits       = 8;
  private int     alphaBits      = 0;

  // Support for transparent windows containing OpenGL content
  private boolean backgroundOpaque = true;
  private int     transparentValueRed = 0;
  private int     transparentValueGreen = 0;
  private int     transparentValueBlue = 0;
  private int     transparentValueAlpha = 0;

  // Switch for on- or offscreen
  private boolean onscreen  = true;

  // offscreen bitmap mode
  private boolean isBitmap  = false;

  /** Creates a Capabilities object. All attributes are in a default
      state.
    */
  public Capabilities() {}

  @Override
  public Object cloneMutable() {
    return clone();
  }

  @Override
  public Object clone() {
    try {
      return super.clone();
    } catch (final CloneNotSupportedException e) {
      throw new NativeWindowException(e);
    }
  }

  /**
   * Copies all {@link Capabilities} values
   * from <code>source</code> into this instance.
   * @return this instance
   */
  public Capabilities copyFrom(final CapabilitiesImmutable other) {
    redBits = other.getRedBits();
    greenBits = other.getGreenBits();
    blueBits = other.getBlueBits();
    alphaBits = other.getAlphaBits();
    backgroundOpaque = other.isBackgroundOpaque();
    onscreen = other.isOnscreen();
    isBitmap = other.isBitmap();
    transparentValueRed = other.getTransparentRedValue();
    transparentValueGreen = other.getTransparentGreenValue();
    transparentValueBlue = other.getTransparentBlueValue();
    transparentValueAlpha = other.getTransparentAlphaValue();
    return this;
  }

  @Override
  public int hashCode() {
    // 31 * x == (x << 5) - x
    int hash = 31 + this.redBits;
    hash = ((hash << 5) - hash) + ( this.onscreen ? 1 : 0 );
    hash = ((hash << 5) - hash) + ( this.isBitmap ? 1 : 0 );
    hash = ((hash << 5) - hash) + this.greenBits;
    hash = ((hash << 5) - hash) + this.blueBits;
    hash = ((hash << 5) - hash) + this.alphaBits;
    hash = ((hash << 5) - hash) + ( this.backgroundOpaque ? 1 : 0 );
    hash = ((hash << 5) - hash) + this.transparentValueRed;
    hash = ((hash << 5) - hash) + this.transparentValueGreen;
    hash = ((hash << 5) - hash) + this.transparentValueBlue;
    hash = ((hash << 5) - hash) + this.transparentValueAlpha;
    return hash;
  }

  private static boolean checkSameSuppSameValue(final VIDType type, final VisualIDHolder a, final VisualIDHolder b) {
        final boolean has_a = a.isVisualIDSupported(type);
        if( has_a != b.isVisualIDSupported(type) ) {
            // both should either support or not support the extra X11_FBCONFIG
            return false;
        }
        return !has_a || a.getVisualID(type) == b.getVisualID(type);
  }
  private static boolean checkSameValueIfBothSupp(final VIDType type, final VisualIDHolder a, final VisualIDHolder b) {
        final boolean has_a = a.isVisualIDSupported(type);
        if( has_a && has_a == b.isVisualIDSupported(type) ) {
            return a.getVisualID(type) == b.getVisualID(type);
        }
        return true;
  }

  @Override
  public boolean equals(final Object obj) {
    if(this == obj)  { return true; }
    if(!(obj instanceof CapabilitiesImmutable)) {
        return false;
    }
    final CapabilitiesImmutable other = (CapabilitiesImmutable)obj;
    {
        // first check whether the VID is compatible
        final int id_t = this.getVisualID(VIDType.NATIVE);
        final int id_o = other.getVisualID(VIDType.NATIVE);
        if( id_t != id_o ) {
            return false;
        }
        if( !checkSameSuppSameValue(VIDType.X11_FBCONFIG, this, other) ) {
            return false;
        }
        if( !checkSameValueIfBothSupp(VIDType.EGL_CONFIG, this, other) ) {
            return false;
        }
    }
    boolean res = other.getRedBits()==redBits &&
                  other.getGreenBits()==greenBits &&
                  other.getBlueBits()==blueBits &&
                  other.getAlphaBits()==alphaBits &&
                  other.isBackgroundOpaque()==backgroundOpaque &&
                  other.isOnscreen()==onscreen &&
                  other.isBitmap()==isBitmap;
    if(res && !backgroundOpaque) {
     res = other.getTransparentRedValue()==transparentValueRed &&
           other.getTransparentGreenValue()==transparentValueGreen &&
           other.getTransparentBlueValue()==transparentValueBlue &&
           other.getTransparentAlphaValue()==transparentValueAlpha;
    }

    return res;
  }

  /**
   * Comparing RGBA values only
   **/
  @Override
  public int compareTo(final CapabilitiesImmutable caps) {
    /**
    if ( ! ( o instanceof CapabilitiesImmutable ) ) {
        Class<?> c = (null != o) ? o.getClass() : null ;
        throw new ClassCastException("Not a CapabilitiesImmutable object, but " + c);
    }
    final CapabilitiesImmutable caps = (CapabilitiesImmutable) o; */

    final int rgba = redBits * greenBits * blueBits * ( alphaBits + 1 );

    final int xrgba = caps.getRedBits() * caps.getGreenBits() * caps.getBlueBits() * ( caps.getAlphaBits() + 1 );

    if(rgba > xrgba) {
        return 1;
    } else if(rgba < xrgba) {
        return -1;
    }

    return 0; // they are equal: RGBA
  }

  @Override
  public int getVisualID(final VIDType type) throws NativeWindowException {
      switch(type) {
          case INTRINSIC:
          case NATIVE:
              return VisualIDHolder.VID_UNDEFINED;
          default:
              throw new NativeWindowException("Invalid type <"+type+">");
      }
  }

  @Override
  public boolean isVisualIDSupported(final VIDType type) {
      switch(type) {
          case INTRINSIC:
          case NATIVE:
              return true;
          default:
              return false;
      }
  }

  @Override
  public final int getRedBits() {
    return redBits;
  }

  /** Sets the number of bits requested for the color buffer's red
      component. On some systems only the color depth, which is the
      sum of the red, green, and blue bits, is considered. */
  public void setRedBits(final int redBits) {
    this.redBits = redBits;
  }

  @Override
  public final int getGreenBits() {
    return greenBits;
  }

  /** Sets the number of bits requested for the color buffer's green
      component. On some systems only the color depth, which is the
      sum of the red, green, and blue bits, is considered. */
  public void setGreenBits(final int greenBits) {
    this.greenBits = greenBits;
  }

  @Override
  public final int getBlueBits() {
    return blueBits;
  }

  /** Sets the number of bits requested for the color buffer's blue
      component. On some systems only the color depth, which is the
      sum of the red, green, and blue bits, is considered. */
  public void setBlueBits(final int blueBits) {
    this.blueBits = blueBits;
  }

  @Override
  public final int getAlphaBits() {
    return alphaBits;
  }

  /**
   * Sets the number of bits requested for the color buffer's alpha
   * component. On some systems only the color depth, which is the
   * sum of the red, green, and blue bits, is considered.
   * <p>
   * <b>Note:</b> If alpha bits are <code>zero</code>, they are set to <code>one</code>
   * by {@link #setBackgroundOpaque(boolean)} and it's OpenGL specialization <code>GLCapabilities::setSampleBuffers(boolean)</code>.<br/>
   * Ensure to call this method after the above to ensure a <code>zero</code> value.</br>
   * The above automated settings takes into account, that the user calls this method to <i>request</i> alpha bits,
   * not to <i>reflect</i> a current state. Nevertheless if this is the case - call it at last.
   * </p>
   */
  public void setAlphaBits(final int alphaBits) {
    this.alphaBits = alphaBits;
  }

  /**
   * Sets whether the surface shall be opaque or translucent.
   * <p>
   * Platform implementations may need an alpha component in the surface (eg. Windows),
   * or expect pre-multiplied alpha values (eg. X11/XRender).<br>
   * To unify the experience, this method also invokes {@link #setAlphaBits(int) setAlphaBits(1)}
   * if {@link #getAlphaBits()} == 0.<br>
   * Please note that in case alpha is required on the platform the
   * clear color shall have an alpha lower than 1.0 to allow anything shining through.
   * </p>
   * <p>
   * Mind that translucency may cause a performance penalty
   * due to the composite work required by the window manager.
   * </p>
   */
  public void setBackgroundOpaque(final boolean opaque) {
      backgroundOpaque = opaque;
      if(!opaque && getAlphaBits()==0) {
          setAlphaBits(1);
      }
  }

  @Override
  public final boolean isBackgroundOpaque() {
    return backgroundOpaque;
  }

  /**
   * Sets whether the surface shall be on- or offscreen.
   * <p>
   * Defaults to true.
   * </p>
   * <p>
   * If requesting an offscreen surface without further selection of it's mode,
   * e.g. FBO, Pbuffer or {@link #setBitmap(boolean) bitmap},
   * the implementation will choose the best available offscreen mode.
   * </p>
   * @param onscreen
   */
  public void setOnscreen(final boolean onscreen) {
    this.onscreen=onscreen;
  }

  @Override
  public final boolean isOnscreen() {
    return onscreen;
  }

  /**
   * Requesting offscreen bitmap mode.
   * <p>
   * If enabled this method also invokes {@link #setOnscreen(int) setOnscreen(false)}.
   * </p>
   * <p>
   * Defaults to false.
   * </p>
   * <p>
   * Requesting offscreen bitmap mode disables the offscreen auto selection.
   * </p>
   */
  public void setBitmap(final boolean enable) {
    if(enable) {
      setOnscreen(false);
    }
    isBitmap = enable;
  }

  @Override
  public boolean isBitmap() {
    return isBitmap;
  }

  @Override
  public final int getTransparentRedValue() { return transparentValueRed; }

  @Override
  public final int getTransparentGreenValue() { return transparentValueGreen; }

  @Override
  public final int getTransparentBlueValue() { return transparentValueBlue; }

  @Override
  public final int getTransparentAlphaValue() { return transparentValueAlpha; }

  /** Sets the transparent red value for the frame buffer configuration,
      ranging from 0 to the maximum frame buffer value for red.
      This value is ignored if {@link #isBackgroundOpaque()} equals true.<br>
      It defaults to half of the frambuffer value for red. <br>
      A value of -1 is interpreted as any value. */
  public void setTransparentRedValue(final int transValueRed) { transparentValueRed=transValueRed; }

  /** Sets the transparent green value for the frame buffer configuration,
      ranging from 0 to the maximum frame buffer value for green.
      This value is ignored if {@link #isBackgroundOpaque()} equals true.<br>
      It defaults to half of the frambuffer value for green.<br>
      A value of -1 is interpreted as any value. */
  public void setTransparentGreenValue(final int transValueGreen) { transparentValueGreen=transValueGreen; }

  /** Sets the transparent blue value for the frame buffer configuration,
      ranging from 0 to the maximum frame buffer value for blue.
      This value is ignored if {@link #isBackgroundOpaque()} equals true.<br>
      It defaults to half of the frambuffer value for blue.<br>
      A value of -1 is interpreted as any value. */
  public void setTransparentBlueValue(final int transValueBlue) { transparentValueBlue=transValueBlue; }

  /** Sets the transparent alpha value for the frame buffer configuration,
      ranging from 0 to the maximum frame buffer value for alpha.
      This value is ignored if {@link #isBackgroundOpaque()} equals true.<br>
      It defaults to half of the frambuffer value for alpha.<br>
      A value of -1 is interpreted as any value. */
  public void setTransparentAlphaValue(final int transValueAlpha) { transparentValueAlpha=transValueAlpha; }

  @Override
  public StringBuilder toString(final StringBuilder sink) {
      return toString(sink, true);
  }

  /** Returns a textual representation of this Capabilities
      object. */
  @Override
  public String toString() {
    final StringBuilder msg = new StringBuilder();
    msg.append("Caps[");
    toString(msg);
    msg.append("]");
    return msg.toString();
  }

  /** Return a textual representation of this object's on/off screen state. Use the given StringBuilder [optional]. */
  protected StringBuilder onoffScreenToString(StringBuilder sink) {
    if(null == sink) {
        sink = new StringBuilder();
    }
    if(onscreen) {
        sink.append("on-scr");
    } else {
        sink.append("offscr[");
    }
    if(isBitmap) {
        sink.append("bitmap");
    } else if(onscreen) {
        sink.append(".");        // no additional off-screen modes besides on-screen
    } else {
        sink.append("auto-cfg"); // auto-config off-screen mode
    }
    sink.append("]");

    return sink;
  }

  /** Element separator */
  protected static final String ESEP = "/";
  /** Component separator */
  protected static final String CSEP = ", ";

  protected StringBuilder toString(StringBuilder sink, final boolean withOnOffScreen) {
    if(null == sink) {
        sink = new StringBuilder();
    }
    sink.append("rgba ").append(redBits).append(ESEP).append(greenBits).append(ESEP).append(blueBits).append(ESEP).append(alphaBits);
    if(backgroundOpaque) {
        sink.append(", opaque");
    } else {
        sink.append(", trans-rgba 0x").append(toHexString(transparentValueRed)).append(ESEP).append(toHexString(transparentValueGreen)).append(ESEP).append(toHexString(transparentValueBlue)).append(ESEP).append(toHexString(transparentValueAlpha));
    }
    if(withOnOffScreen) {
        sink.append(CSEP);
        onoffScreenToString(sink);
    }
    return sink;
  }

  protected final String toHexString(final int val) { return Integer.toHexString(val); }
}
