/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import Geometry from 'ol/geom/Geometry';
import type { ProjectionLike } from 'ol/proj';

/**
 * One-way projection interface for geometry transformation from a source coordinate reference system (CRS).
 */
export interface From<T extends Geometry> {
  /**
   * Projection of a geometry from the given source coordinate reference system (CRS).
   * @param sourceProjection The source CRS.
   * @returns The geometry to transform.
   */
  from(sourceProjection: ProjectionLike): (geometry: T) => T;
}

/**
 * One-way projection interface for geometry transformation to a target coordinate reference system (CRS).
 */
export interface To<T extends Geometry> {
  /**
   * Projection of a geometry to the given target coordinate reference system (CRS).
   * @param targetProjection The target CRS.
   * @returns The geometry to transform.
   */
  to(targetProjection: ProjectionLike): (geometry: T) => T;
}

/**
 * Two-Way projection interface for geometry transformation.
 */
export interface Projection<T extends Geometry> extends From<T>, To<T> {}

/**
 * List of frequently used coordinate reference systems (CRS) for projections.
 */
export enum Projections {
  /**
   * Typical coordinate reference system used for longitude-latitude values on the WGS84 world sphere.
   */
  EPSG4326_WGS84 = 'EPSG:4326',
  /**
   * Typical 2D projection of EPSG:4326 used for maps.
   */
  EPSG3857_WGS84PROJECTED = 'EPSG:3857',
  /**
   * GeoJson as defined in RFC7946 defines WGS84 as its one and only coordinate reference system.
   */
  /* eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values */
  GEOJSON = 'EPSG:4326',
}

/**
 * Create a projection for a basecoordinate reference system (CRS).
 * @param baseProjection The CRS to project from or to.
 * @returns The projection.
 */
export function projection<T extends Geometry>(baseProjection: ProjectionLike): Projection<T> {
  return {
    // from source to base
    from(sourceProjection) {
      return projectionFrom<T>(sourceProjection).to(baseProjection);
    },
    // from base to target
    to(targetProjection) {
      return projectionFrom<T>(baseProjection).to(targetProjection);
    },
  };
}

/**
 * Create a projection from source to target coordinate reference system (CRS).
 * @param targetProjection The target CRS.
 * @returns The projection with a fixed target CRS.
 */
export function projectionTo<T extends Geometry>(targetProjection: ProjectionLike): From<T> {
  return {
    from(sourceProjection) {
      return (geometry: T) => <T>geometry.transform(sourceProjection, targetProjection);
    },
  };
}

/**
 * Create a projection from source to target coordinate reference system (CRS).
 * @param sourceProjection The source CRS.
 * @returns The projection with a fixed source CRS.
 */
export function projectionFrom<T extends Geometry>(sourceProjection: ProjectionLike): To<T> {
  return {
    to(targetProjection) {
      return (geometry: T) => <T>geometry.transform(sourceProjection, targetProjection);
    },
  };
}
