/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sis.geometry;


/*
 * Do not add dependency to java.awt.Rectangle2D in this class, because not every platforms
 * support Java2D (e.g. Android),  or applications that do not need it may want to avoid to
 * force installation of the Java2D module (e.g. JavaFX/SWT).
 */
import java.io.Serializable;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.metadata.extent.GeographicBoundingBox;

import static org.apache.sis.util.ArgumentChecks.ensureDimensionMatches;


/**
 * An immutable {@code Envelope} (a minimum bounding box or rectangle) of arbitrary dimension.
 * This class is final in order to ensure that the immutability contract cannot be broken
 * (assuming not using <i>Java Native Interface</i> or reflections).
 *
 * <h2>Immutability and thread safety</h2>
 * This final class is immutable and thus inherently thread-safe if the {@link CoordinateReferenceSystem}
 * instance given to the constructor is immutable. This is usually the case in Apache SIS.
 *
 * @author  Cédric Briançon (Geomatys)
 * @author  Martin Desruisseaux (IRD, Geomatys)
 * @version 0.3
 * @since   0.3
 */
public final class ImmutableEnvelope extends ArrayEnvelope implements Serializable {
    /**
     * For cross-version compatibility.
     */
    private static final long serialVersionUID = 8740224085449107870L;

    /**
     * Constructs an envelope defined by two corners given as direct positions.
     * The envelope CRS will be the CRS of the given positions.
     *
     * @param  lowerCorner  the limits in the direction of decreasing coordinate values for each dimension.
     * @param  upperCorner  the limits in the direction of increasing coordinate values for each dimension.
     * @throws MismatchedDimensionException if the two positions do not have the same dimension.
     * @throws MismatchedReferenceSystemException if the CRS of the two position are not equal.
     */
    public ImmutableEnvelope(final DirectPosition lowerCorner, final DirectPosition upperCorner)
            throws MismatchedDimensionException, MismatchedReferenceSystemException
    {
        super(lowerCorner, upperCorner);
    }

    /**
     * Constructs an envelope defined by two corners given as sequences of coordinate values.
     *
     * @param  lowerCorner  the limits in the direction of decreasing coordinate values for each dimension.
     * @param  upperCorner  the limits in the direction of increasing coordinate values for each dimension.
     * @param  crs          the CRS to assign to this envelope, or {@code null}.
     * @throws MismatchedDimensionException if the two sequences do not have the same length, or
     *         if the dimension of the given CRS is not equals to the dimension of the given corners.
     */
    public ImmutableEnvelope(final double[] lowerCorner, final double[] upperCorner,
            final CoordinateReferenceSystem crs) throws MismatchedDimensionException
    {
        super(lowerCorner, upperCorner);
        this.crs = crs;
        ensureDimensionMatches("crs", getDimension(), crs);
    }

    /**
     * Constructs a new envelope with the same data as the specified geographic bounding box.
     * The coordinate reference system is set to the
     * {@linkplain org.apache.sis.referencing.CommonCRS#defaultGeographic() default geographic CRS}.
     * Axis order is (<var>longitude</var>, <var>latitude</var>).
     *
     * @param box  the bounding box to copy.
     */
    public ImmutableEnvelope(final GeographicBoundingBox box) {
        super(box);
    }

    /**
     * Creates an immutable envelope with the values of the given envelope.
     * This constructor can be used when the given envelope is known to not
     * be an instance of {@code ImmutableEnvelope}. In case of doubt,
     * consider using {@link #castOrCopy(Envelope)} instead.
     *
     * @param envelope  the envelope to copy.
     *
     * @see #castOrCopy(Envelope)
     */
    public ImmutableEnvelope(final Envelope envelope) {
        super(envelope);
    }

    /**
     * Creates an immutable envelope with the coordinate values of the given envelope but
     * a different CRS. This method does <strong>not</strong> reproject the given envelope.
     * It just assign the given CRS to this envelope without any check, except for the CRS
     * dimension.
     *
     * <p>The main purpose of this method is to assign a non-null CRS when the envelope to
     * copy has a null CRS.</p>
     *
     * @param  crs       the CRS to assign to this envelope, or {@code null}.
     * @param  envelope  the envelope from which to copy coordinate values.
     * @throws MismatchedDimensionException if the dimension of the given CRS is not equals
     *         to the dimension of the given envelope.
     */
    public ImmutableEnvelope(final CoordinateReferenceSystem crs, final Envelope envelope)
            throws MismatchedDimensionException
    {
        super(envelope);
        this.crs = crs;
        ensureDimensionMatches("crs", getDimension(), crs);
    }

    /**
     * Constructs a new envelope initialized to the values parsed from the given string in
     * {@code BOX} or <i>Well Known Text</i> (WKT) format. The given string is typically
     * a {@code BOX} element like below:
     *
     * {@snippet lang="wkt" :
     *   BOX(-180 -90, 180 90)
     *   }
     *
     * However, this constructor is lenient to other geometry types like {@code POLYGON}.
     * See the javadoc of the {@link GeneralEnvelope#GeneralEnvelope(CharSequence) GeneralEnvelope}
     * constructor for more information.
     *
     * @param  crs  the coordinate reference system, or {@code null} if none.
     * @param  wkt  the {@code BOX}, {@code POLYGON} or other kind of element to parse.
     * @throws IllegalArgumentException if the given string cannot be parsed.
     * @throws MismatchedDimensionException if the dimension of the given CRS is not equals
     *         to the dimension of the parsed envelope.
     */
    public ImmutableEnvelope(final CoordinateReferenceSystem crs, final CharSequence wkt)
            throws IllegalArgumentException, MismatchedDimensionException
    {
        super(wkt);
        this.crs = crs;
        ensureDimensionMatches("crs", getDimension(), crs);
    }

    /**
     * Returns the given envelope as an {@code ImmutableEnvelope} instance. If the given envelope
     * is already an instance of {@code ImmutableEnvelope}, then it is returned unchanged.
     * Otherwise the coordinate values and the CRS of the given envelope are copied in a
     * new envelope.
     *
     * @param  envelope  the envelope to cast, or {@code null}.
     * @return the values of the given envelope as an {@code ImmutableEnvelope} instance.
     *
     * @see AbstractEnvelope#castOrCopy(Envelope)
     * @see GeneralEnvelope#castOrCopy(Envelope)
     */
    public static ImmutableEnvelope castOrCopy(final Envelope envelope) {
        if (envelope == null || envelope instanceof ImmutableEnvelope) {
            return (ImmutableEnvelope) envelope;
        }
        return new ImmutableEnvelope(envelope);
    }
}
