// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2024 MariaDB Corporation Ab
package org.mariadb.jdbc.type;

import java.sql.SQLDataException;
import org.mariadb.jdbc.client.Column;
import org.mariadb.jdbc.client.ReadableByteBuf;

/** Geometry parser */
public interface Geometry {

  /**
   * Parse point from packet
   *
   * @param littleEndian encoding order
   * @param buf packet buffer
   * @return point value
   */
  static Point parsePoint(boolean littleEndian, ReadableByteBuf buf) {
    double x = littleEndian ? buf.readDouble() : buf.readDoubleBE();
    double y = littleEndian ? buf.readDouble() : buf.readDoubleBE();
    return new Point(x, y);
  }

  /**
   * parse geometry object
   *
   * @param buf packet buffer
   * @param length data length
   * @param column column meta
   * @return geometry parsed object
   * @throws SQLDataException if parsing exception occurs
   */
  static Geometry getGeometry(ReadableByteBuf buf, int length, Column column)
      throws SQLDataException {
    if (length == 0) return null;
    boolean littleEndian = buf.readByte() == 0x01;
    int dataType = littleEndian ? buf.readInt() : buf.readIntBE();
    switch (dataType) {
      case 1:
        // wkbPoint
        return parsePoint(littleEndian, buf);

      case 2:
        // wkbLineString
        int pointNumber = littleEndian ? buf.readInt() : buf.readIntBE();
        Point[] points = new Point[pointNumber];
        for (int i = 0; i < pointNumber; i++) {
          points[i] = parsePoint(littleEndian, buf);
        }
        return new LineString(points, true);

      case 3:
        // wkbPolygon
        int numRings = littleEndian ? buf.readInt() : buf.readIntBE();
        LineString[] lines = new LineString[numRings];
        for (int i = 0; i < numRings; i++) {
          int pointNb = littleEndian ? buf.readInt() : buf.readIntBE();
          Point[] lsPoints = new Point[pointNb];
          for (int j = 0; j < pointNb; j++) {
            lsPoints[j] = parsePoint(littleEndian, buf);
          }
          lines[i] = new LineString(lsPoints, false);
        }
        return new Polygon(lines);

      case 4:
        // wkbMultiPoint
        int pointNb = littleEndian ? buf.readInt() : buf.readIntBE();
        Point[] pointArr = new Point[pointNb];
        for (int i = 0; i < pointNb; i++) {
          pointArr[i] = (Point) getGeometry(buf, length, column);
        }
        return new MultiPoint(pointArr);

      case 5:
        // wkbMultiLinestring
        int multiNb = littleEndian ? buf.readInt() : buf.readIntBE();
        LineString[] multiLines = new LineString[multiNb];
        for (int i = 0; i < multiNb; i++) {
          multiLines[i] = (LineString) getGeometry(buf, length, column);
        }
        return new MultiLineString(multiLines);

      case 6:
        // wkbMultiPolygon
        int multiPolyNb = littleEndian ? buf.readInt() : buf.readIntBE();
        Polygon[] multiPolygons = new Polygon[multiPolyNb];
        for (int i = 0; i < multiPolyNb; i++) {
          multiPolygons[i] = (Polygon) getGeometry(buf, length, column);
        }
        return new MultiPolygon(multiPolygons);

      case 7:
        // wkbGeometryCollection
        int multiCollNb = littleEndian ? buf.readInt() : buf.readIntBE();
        Geometry[] multiGeos = new Geometry[multiCollNb];
        for (int i = 0; i < multiCollNb; i++) {
          multiGeos[i] = getGeometry(buf, length, column);
        }
        return new GeometryCollection(multiGeos);

      default:
        // ERROR
        buf.skip(length - 5);
        throw new SQLDataException(
            String.format("Data type %s cannot be decoded as Geometry", column.getType()));
    }
  }
}
