/**
   * Creates the {@link RenderedImage} corresponding to the tile at index {@code tileIdx} and uses a
   * {@link RenderedImageMapResponse} to encode it into the {@link #getResponseFormat() response
   * format}.
   *
   * @see org.geowebcache.layer.MetaTile#writeTileToStream(int, org.geowebcache.io.Resource)
   * @see RenderedImageMapResponse#write
   */
  @Override
  public boolean writeTileToStream(final int tileIdx, Resource target) throws IOException {

    checkNotNull(metaTileMap, "webMap is not set");
    if (!(metaTileMap instanceof RenderedImageMap)) {
      throw new IllegalArgumentException(
          "Only RenderedImageMaps are supported so far: " + metaTileMap.getClass().getName());
    }
    final RenderedImageMapResponse mapEncoder;
    {
      final GWC mediator = GWC.get();
      final Response responseEncoder = mediator.getResponseEncoder(responseFormat, metaTileMap);
      mapEncoder = (RenderedImageMapResponse) responseEncoder;
    }

    RenderedImage tile = metaTileMap.getImage();
    WMSMapContent tileContext = metaTileMap.getMapContext();

    if (this.tiles.length > 1 || (this.tiles.length == 1 && metaHasGutter())) {
      final Rectangle tileDim = this.tiles[tileIdx];
      tile = createTile(tileDim.x, tileDim.y, tileDim.width, tileDim.height);
      disposeLater(tile);
      {
        final WMSMapContent metaTileContext = metaTileMap.getMapContext();
        // do not create tileContext with metaTileContext.getLayers() as the layer list.
        // It is not needed at this stage and the constructor would force a
        // MapLayer.getBounds() that might fail
        tileContext = new WMSMapContent();
        tileContext.setRequest(metaTileContext.getRequest());
        tileContext.setBgColor(metaTileContext.getBgColor());
        tileContext.setMapWidth(tileDim.width);
        tileContext.setMapHeight(tileDim.height);
        tileContext.setPalette(metaTileContext.getPalette());
        tileContext.setTransparent(tileContext.isTransparent());
        long[][] tileIndexes = getTilesGridPositions();
        BoundingBox tileBounds = gridSubset.boundsFromIndex(tileIndexes[tileIdx]);
        ReferencedEnvelope tilebbox =
            new ReferencedEnvelope(metaTileContext.getCoordinateReferenceSystem());
        tilebbox.init(
            tileBounds.getMinX(), tileBounds.getMaxX(), tileBounds.getMinY(), tileBounds.getMaxY());
        tileContext.getViewport().setBounds(tilebbox);
      }
    }

    OutputStream outStream = target.getOutputStream();
    try {
      // call formatImageOuputStream instead of write to avoid disposition of rendered images
      // when processing a tile from a metatile and instead defer it to this class' dispose()
      // method
      mapEncoder.formatImageOutputStream(tile, outStream, tileContext);
      return true;
    } finally {
      outStream.close();
    }
  }
    @Override
    public void validate(IValidatable<Set<XMLGridSubset>> validatable) {
      if (!validate) {
        return;
      }
      Set<XMLGridSubset> gridSubsets = validatable.getValue();
      if (gridSubsets == null || gridSubsets.size() == 0) {
        error(validatable, "GridSubsetsEditor.validation.empty");
        return;
      }

      final GWC gwc = GWC.get();
      for (XMLGridSubset subset : gridSubsets) {
        final String gridSetName = subset.getGridSetName();
        final Integer zoomStart = subset.getZoomStart();
        final Integer zoomStop = subset.getZoomStop();
        final BoundingBox extent = subset.getExtent();

        if (gridSetName == null) {
          throw new IllegalStateException("GridSet name is null");
        }

        if (zoomStart != null && zoomStop != null) {
          if (zoomStart.intValue() > zoomStop.intValue()) {
            error(validatable, "GridSubsetsEditor.validation.zoomLevelsError");
            return;
          }
        }

        final GridSetBroker gridSetBroker = gwc.getGridSetBroker();
        final GridSet gridSet = gridSetBroker.get(gridSetName);

        if (null == gridSet) {
          error(validatable, "GridSubsetsEditor.validation.gridSetNotFound", gridSetName);
          return;
        }

        if (extent != null) {
          if (extent.isNull() || !extent.isSane()) {
            error(validatable, "GridSubsetsEditor.validation.invalidBounds");
          }
          final BoundingBox fullBounds = gridSet.getOriginalExtent();
          final boolean intersects = fullBounds.intersects(extent);
          if (!intersects) {
            error(validatable, "GridSubsetsEditor.validation.boundsOutsideCoverage");
          }
        }
      }
    }
  public GridSubsetsEditor(final String id, final IModel<Set<XMLGridSubset>> model) {
    super(id, model);
    add(validator = new GridSubsetListValidator());

    // container for ajax updates
    final WebMarkupContainer container = new WebMarkupContainer("container");
    container.setOutputMarkupId(true);
    add(container);

    // the link list
    table = new WebMarkupContainer("table");
    table.setOutputMarkupId(true);

    container.add(table);

    grids =
        new ListView<XMLGridSubset>(
            "gridSubsets", new ArrayList<XMLGridSubset>(model.getObject())) {

          private static final long serialVersionUID = 1L;

          @Override
          protected void onBeforeRender() {
            super.onBeforeRender();
          }

          @Override
          protected void populateItem(final ListItem<XMLGridSubset> item) {
            // odd/even style
            final int index = item.getIndex();
            item.add(new SimpleAttributeModifier("class", index % 2 == 0 ? "even" : "odd"));

            final XMLGridSubset gridSubset = item.getModelObject();
            GridSetBroker gridSetBroker = GWC.get().getGridSetBroker();

            String gridsetDescription = null;
            int gridsetLevels;
            boolean gridsetExists;
            {
              final GridSet gridSet = gridSetBroker.get(gridSubset.getGridSetName());
              gridsetExists = gridSet != null;
              if (gridsetExists) {
                gridsetLevels = gridSet.getNumLevels();
                gridsetDescription = gridSet.getDescription();
              } else {
                gridsetLevels =
                    gridSubset.getZoomStop() == null ? 1 : gridSubset.getZoomStop().intValue();
              }
            }
            final Label gridSetLabel;
            final Component gridSetBounds;

            gridSetLabel =
                new Label("gridSet", new PropertyModel<String>(item.getModel(), "gridSetName"));
            if (!gridsetExists) {
              gridSetLabel.add(
                  new AttributeModifier(
                      "style", true, new Model<String>("color:red;text-decoration:line-through;")));
              getPage().warn("GridSet " + gridSubset.getGridSetName() + " does not exist");
            }
            item.add(gridSetLabel);
            if (null != gridsetDescription) {
              gridSetLabel.add(
                  new AttributeModifier("title", true, new Model<String>(gridsetDescription)));
            }

            final Component removeLink;

            final int maxZoomLevel = gridsetLevels - 1;
            final ArrayList<Integer> zoomLevels = new ArrayList<Integer>(maxZoomLevel + 1);
            for (int z = 0; z <= maxZoomLevel; z++) {
              zoomLevels.add(Integer.valueOf(z));
            }

            // zoomStart has all zoom levels as choices
            // zoomStop choices start at zoomStart's selection
            // minCachedLevel start at zoomStart's selection and ends at zoomStop's selection
            // maxCachedLevel start at minCachedLevels' and ends at zoomStop's selection

            final IModel<Integer> zoomStartModel;
            final IModel<Integer> zoomStopModel;
            final IModel<Integer> minCachedLevelModel;
            final IModel<Integer> maxCachedLevelModel;

            final ZoomLevelDropDownChoice zoomStart;
            final ZoomLevelDropDownChoice zoomStop;
            final ZoomLevelDropDownChoice minCachedLevel;
            final ZoomLevelDropDownChoice maxCachedLevel;

            zoomStartModel = new PropertyModel<Integer>(item.getModel(), "zoomStart");
            zoomStopModel = new PropertyModel<Integer>(item.getModel(), "zoomStop");
            minCachedLevelModel = new PropertyModel<Integer>(item.getModel(), "minCachedLevel");
            maxCachedLevelModel = new PropertyModel<Integer>(item.getModel(), "maxCachedLevel");

            @SuppressWarnings({"rawtypes", "unchecked"})
            final IModel<List<Integer>> allLevels = new Model(zoomLevels);

            zoomStart = new ZoomLevelDropDownChoice("zoomStart", zoomStartModel, allLevels);
            zoomStart.setEnabled(gridsetExists);
            item.add(zoomStart);

            zoomStop = new ZoomLevelDropDownChoice("zoomStop", zoomStopModel, allLevels);
            zoomStop.setEnabled(gridsetExists);
            item.add(zoomStop);

            minCachedLevel =
                new ZoomLevelDropDownChoice("minCachedLevel", minCachedLevelModel, allLevels);
            minCachedLevel.setEnabled(gridsetExists);
            item.add(minCachedLevel);

            maxCachedLevel =
                new ZoomLevelDropDownChoice("maxCachedLevel", maxCachedLevelModel, allLevels);
            maxCachedLevel.setEnabled(gridsetExists);
            item.add(maxCachedLevel);

            for (ZoomLevelDropDownChoice dropDown :
                Arrays.asList(zoomStart, zoomStop, minCachedLevel, maxCachedLevel)) {
              dropDown.add(
                  new OnChangeAjaxBehavior() {
                    private static final long serialVersionUID = 1L;

                    // cascades to zoomStop, min and max cached levels
                    @Override
                    protected void onUpdate(AjaxRequestTarget target) {
                      updateValidZoomRanges(
                          zoomStart, zoomStop, minCachedLevel, maxCachedLevel, target);
                    }
                  });
            }

            updateValidZoomRanges(zoomStart, zoomStop, minCachedLevel, maxCachedLevel, null);

            // gridSetBounds = new EnvelopePanel("bounds");
            // gridSetBounds.setCRSFieldVisible(false);
            // gridSetBounds.setCrsRequired(false);
            // gridSetBounds.setLabelsVisibility(false);
            // gridSetBounds.setModel(new Model<ReferencedEnvelope>(new ReferencedEnvelope()));
            gridSetBounds =
                new Label("bounds", new ResourceModel("GridSubsetsEditor.bounds.dynamic"));
            item.add(gridSetBounds);

            removeLink =
                new ImageAjaxLink("removeLink", GWCIconFactory.DELETE_ICON) {
                  private static final long serialVersionUID = 1L;

                  @Override
                  protected void onClick(AjaxRequestTarget target) {
                    List<XMLGridSubset> list;
                    list = new ArrayList<XMLGridSubset>(grids.getModelObject());
                    final XMLGridSubset subset = (XMLGridSubset) getDefaultModelObject();

                    list.remove(subset);

                    grids.setModelObject(list);

                    List<String> choices = new ArrayList<String>(availableGridSets.getChoices());
                    choices.add(subset.getGridSetName());
                    Collections.sort(choices);
                    availableGridSets.setChoices(choices);

                    target.addComponent(container);
                    target.addComponent(availableGridSets);
                  }
                };
            removeLink.setDefaultModel(item.getModel());
            removeLink.add(
                new AttributeModifier(
                    "title", true, new ResourceModel("GridSubsetsEditor.removeLink")));
            item.add(removeLink);
          }
        };

    grids.setOutputMarkupId(true);
    // this is necessary to avoid loosing item contents on edit/validation checks
    grids.setReuseItems(true);
    table.add(grids);

    List<String> gridSetNames = new ArrayList<String>(GWC.get().getGridSetBroker().getNames());
    for (XMLGridSubset gs : model.getObject()) {
      gridSetNames.remove(gs.getGridSetName());
    }
    Collections.sort(gridSetNames);

    GeoServerAjaxFormLink addGridsubsetLink =
        new GeoServerAjaxFormLink("addGridSubset") {
          private static final long serialVersionUID = 1L;

          @Override
          protected void onClick(AjaxRequestTarget target, Form form) {
            availableGridSets.processInput();

            final String selectedGridset = availableGridSets.getModelObject();
            if (null == selectedGridset) {
              return;
            }

            List<String> choices = new ArrayList<String>(availableGridSets.getChoices());
            choices.remove(selectedGridset);
            availableGridSets.setChoices(choices);
            availableGridSets.setEnabled(!choices.isEmpty());

            XMLGridSubset newSubset = new XMLGridSubset();
            newSubset.setGridSetName(selectedGridset);
            grids.getModelObject().add(newSubset);

            target.addComponent(table);
            target.addComponent(availableGridSets);
          }
        };
    addGridsubsetLink.add(new Icon("addIcon", GWCIconFactory.ADD_ICON));
    add(addGridsubsetLink);

    availableGridSets =
        new DropDownChoice<String>("availableGridsets", new Model<String>(), gridSetNames);
    availableGridSets.setOutputMarkupId(true);
    add(availableGridSets);
  }
 @Override
 protected TileLayer load() {
   GWC facade = GWC.get();
   return facade.getTileLayerByName(name);
 }
 /** @see org.geoserver.web.wicket.GeoServerDataProvider#getItems() */
 @Override
 protected List<CachedLayerInfo> getItems() {
   GWC gwc = GWC.get();
   return CachedLayerDetachableModel.getItems(gwc);
 }