/*
 * Copyright (C) 2014 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration. All Rights Reserved.
 */
/**
 * @exports GeographicProjection
 * @version $Id: GeographicProjection.js 2821 2015-02-20 16:59:27Z dcollins $
 */
define([
        '../error/ArgumentError',
        '../util/Logger',
        '../geom/Sector',
        '../error/UnsupportedOperationError'
    ],
    function (ArgumentError,
              Logger,
              Sector,
              UnsupportedOperationError) {
        "use strict";

        /**
         * Constructs a base geographic projection.
         * @alias GeographicProjection
         * @constructor
         * @classdesc Represents a geographic projection.
         * This is an abstract class and is meant to be instantiated only by subclasses.
         * See the following projections:
         * <ul>
         *     <li>{@link ProjectionEquirectangular}</li>
         *     <li>{@link ProjectionMercator}</li>
         *     <li>{@link ProjectionPolarEquidistant}</li>
         *     <li>{@link ProjectionUPS}</li>
         </ul>
         * @param {String} displayName The projection's display name.
         * @param {boolean} continuous Indicates whether this projection is continuous.
         * @param {Sector} projectionLimits This projection's projection limits. May be null to indicate the full
         * range of latitude and longitude, +/- 90 degrees latitude, +/- 180 degrees longitude.
         */
        var GeographicProjection = function (displayName, continuous, projectionLimits) {

            /**
             * This projection's display name.
             * @type {string}
             */
            this.displayName = displayName || "Geographic Projection";

            /**
             * Indicates whether this projection should be treated as continuous with itself. If true, the 2D map
             * will appear to scroll continuously horizontally.
             * @type {boolean}
             * @readonly
             */
            this.continuous = continuous;

            /**
             * Indicates the geographic limits of this projection.
             * @type {Sector}
             * @readonly
             */
            this.projectionLimits = projectionLimits;

            /**
             * Indicates whether this projection is a 2D projection.
             * @type {boolean}
             * @readonly
             */
            this.is2D = true;
        };

        /**
         * Converts a geographic position to Cartesian coordinates.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} latitude The latitude of the position, in degrees.
         * @param {number} longitude The longitude of the position, in degrees.
         * @param {number} elevation The elevation of the position, in meters.
         * @param {Vec3} offset An offset to apply to the Cartesian output. Typically only projections that are
         * continuous (see [continuous]{@link GeographicProjection#continuous}) apply to this offset. Others ignore
         * it. May be null to indicate no offset is applied.
         * @param {Vec3} result A variable in which to store the computed Cartesian point.
         *
         * @returns {Vec3} The specified result argument containing the computed point.
         * @throws {ArgumentError} If the specified globe or result is null or undefined.
         */
        GeographicProjection.prototype.geographicToCartesian = function (globe, latitude, longitude, elevation,
                                                                         offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesian", "abstractInvocation"));
        };

        /**
         * Computes a grid of Cartesian points within a specified sector and relative to a specified Cartesian
         * reference point.
         * <p>
         * This method is used to compute a collection of points within a sector. It is used by tessellators to
         * efficiently generate a tile's interior points. The number of points to generate is indicated by the tileWidth
         * and tileHeight parameters but is one more in each direction. Width refers to the longitudinal direction,
         * height to the latitudinal.
         * <p>
         * For each implied position within the sector, an elevation value is specified via an array of elevations. The
         * calculation at each position incorporates the associated elevation.
         * There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
         *
         * @param {Globe} globe The globe this projection applies to.
         * @param {Sector} sector The sector in which to compute the points.
         * @param {Number} numLat The number of latitudinal sections a tile is divided into.
         * @param {Number} numLon The number of longitudinal sections a tile is divided into.
         * @param {Number[]} elevations An array of elevations to incorporate in the point calculations. There must be
         * one elevation value in the array for each generated point. Elevations are in meters.
         * There must be (tileWidth + 1) x (tileHeight + 1) elevations in the array.
         * @param {Vec3} referencePoint The X, Y and Z Cartesian coordinates to subtract from the computed coordinates.
         * This makes the computed coordinates relative to the specified point. May be null.
         * @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Float32Array} result A typed array to hold the computed coordinates. It must be at least of
         * size (tileWidth + 1) x (tileHeight + 1) * 3.
         * The points are returned in row major order, beginning with the row of minimum latitude.
         * @returns {Float32Array} The specified result argument, populated with the computed Cartesian coordinates.
         * @throws {ArgumentError} if any of the specified globe, sector, elevations array or results arrays is null or
         * undefined.
         */
        GeographicProjection.prototype.geographicToCartesianGrid = function (globe, sector, numLat, numLon, elevations,
                                                                             referencePoint, offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "geographicToCartesianGrid", "abstractInvocation"));
        };

        /**
         * Converts a Cartesian point to a geographic position.
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} offset An offset to apply to the Cartesian output points. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Position} result A variable in which to return the computed position.
         *
         * @returns {Position} The specified result argument containing the computed position.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.cartesianToGeographic = function (globe, x, y, z, offset, result) {
            throw new UnsupportedOperationError(
                Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "cartesianToGeographic", "abstractInvocation"));
        };

        /**
         * Computes a Cartesian vector that points north and is tangent to the meridian at a specified geographic
         * location.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} latitude The latitude of the location, in degrees.
         * @param {number} longitude The longitude of the location, in degrees.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.northTangentAtLocation = function (globe, latitude, longitude, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "northTangentAtLocation", "missingResult"));
            }

            result[0] = 0;
            result[1] = 1;
            result[2] = 0;

            return result;
        };

        /**
         * Computes a Cartesian vector that points north and is tangent to the meridian at a specified Cartesian
         * point.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} offset An offset to apply to the Cartesian point. Typically only projections that
         * are continuous (see [continuous]{@link GeographicProjection#continuous}) apply this offset. Others ignore it.
         * May be null to indicate that no offset is applied.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.northTangentAtPoint = function (globe, x, y, z, offset, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "ProjectionEquirectangular",
                    "northTangentAtPoint", "missingResult"));
            }

            result[0] = 0;
            result[1] = 1;
            result[2] = 0;

            return result;
        };

        /**
         * Computes the Cartesian surface normal vector at a specified Cartesian point.
         *
         * @param {Globe} globe The globe this projection is applied to.
         * @param {number} x The X component of the Cartesian point.
         * @param {number} y The Y component of the Cartesian point.
         * @param {number} z The Z component of the Cartesian point.
         * @param {Vec3} result A variable in which to return the computed vector.
         *
         * @returns{Vec3} The specified result argument containing the computed vector.
         * @throws {ArgumentError} If either the specified globe or result argument is null or undefined.
         */
        GeographicProjection.prototype.surfaceNormalAtPoint = function (globe, x, y, z, result) {
            if (!result) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "GeographicProjection", "surfaceNormalAtPoint",
                    "missingResult"));
            }

            result[0] = 0;
            result[1] = 0;
            result[2] = 1;

            return result;
        };

        return GeographicProjection;
    });