class InfoWindow {

  @NonNull
  InfoWindow open(@NonNull MapView mapView, Marker boundMarker, @NonNull LatLng position, int offsetX, int offsetY) {
    setBoundMarker(boundMarker);

    MapView.LayoutParams lp = new MapView.LayoutParams(MapView.LayoutParams.WRAP_CONTENT,
      MapView.LayoutParams.WRAP_CONTENT);

    MapboxMap mapboxMap = this.mapboxMap.get();
    View view = this.view.get();
    if (view != null && mapboxMap != null) {
      view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

      markerHeightOffset = offsetY;
      markerWidthOffset = -offsetX;

      // Calculate default Android x,y coordinate
      coordinates = mapboxMap.getProjection().toScreenLocation(position);
      float x = coordinates.x - (view.getMeasuredWidth() / 2) + offsetX;
      float y = coordinates.y - view.getMeasuredHeight() + offsetY;

      if (view instanceof BubbleLayout) {
        // only apply repositioning/margin for InfoWindowView
        Resources resources = mapView.getContext().getResources();

        // get right/left popup window
        float rightSideInfowWindow = x + view.getMeasuredWidth();
        float leftSideInfoWindow = x;

        // get right/left map view
        final float mapRight = mapView.getRight();
        final float mapLeft = mapView.getLeft();

        float marginHorizontal = resources.getDimension(R.dimen.mapbox_infowindow_margin);
        float tipViewOffset = resources.getDimension(R.dimen.mapbox_infowindow_tipview_width) / 2;
        float tipViewMarginLeft = view.getMeasuredWidth() / 2 - tipViewOffset;

        boolean outOfBoundsLeft = false;
        boolean outOfBoundsRight = false;

        // only optimise margins if view is inside current viewport
        if (coordinates.x >= 0 && coordinates.x <= mapView.getWidth()
          && coordinates.y >= 0 && coordinates.y <= mapView.getHeight()) {

          // if out of bounds right
          if (rightSideInfowWindow > mapRight) {
            outOfBoundsRight = true;
            x -= rightSideInfowWindow - mapRight;
            tipViewMarginLeft += rightSideInfowWindow - mapRight + tipViewOffset;
            rightSideInfowWindow = x + view.getMeasuredWidth();
          }

          // fit screen left
          if (leftSideInfoWindow < mapLeft) {
            outOfBoundsLeft = true;
            x += mapLeft - leftSideInfoWindow;
            tipViewMarginLeft -= mapLeft - leftSideInfoWindow + tipViewOffset;
            leftSideInfoWindow = x;
          }

          // Add margin right
          if (outOfBoundsRight && mapRight - rightSideInfowWindow < marginHorizontal) {
            x -= marginHorizontal - (mapRight - rightSideInfowWindow);
            tipViewMarginLeft += marginHorizontal - (mapRight - rightSideInfowWindow) - tipViewOffset;
            leftSideInfoWindow = x;
          }

          // Add margin left
          if (outOfBoundsLeft && leftSideInfoWindow - mapLeft < marginHorizontal) {
            x += marginHorizontal - (leftSideInfoWindow - mapLeft);
            tipViewMarginLeft -= (marginHorizontal - (leftSideInfoWindow - mapLeft)) - tipViewOffset;
          }
        }

        // Adjust tipView
        ((BubbleLayout) view).setArrowPosition(tipViewMarginLeft);
      }

      // set anchor popupwindowview
      view.setX(x);
      view.setY(y);

      // Calculate x-offset and y-offset for update method
      viewWidthOffset = x - coordinates.x - offsetX;
      viewHeightOffset = -view.getMeasuredHeight() + offsetY;


      close(); // if it was already opened
      mapView.addView(view, lp);
      isVisible = true;
    }
    return this;
  }

    InfoWindow(MapView mapView, int layoutResId, MapboxMap mapboxMap);
    InfoWindow(@NonNull View view, MapboxMap mapboxMap);

}