import {Vector3 as Vector32} from "three/src/math/Vector3";
import {Group as Group2} from "three/src/objects/Group";
import {Box3 as Box32} from "three/src/math/Box3";
import {CoreObject} from "./Object";
import {CoreGeometry} from "./Geometry";
import {CoreAttribute} from "./Attribute";
import {CoreString} from "../String";
import {CoreConstant, objectTypeFromConstructor} from "./Constant";
import {CoreType} from "../Type";
import {ArrayUtils as ArrayUtils2} from "../ArrayUtils";
export class CoreGroup {
  constructor() {
    this._objects = [];
    this._objects_with_geo = [];
    this.touch();
  }
  timestamp() {
    return this._timestamp;
  }
  touch() {
    this._timestamp = performance.now();
    this.reset();
  }
  reset() {
    this._bounding_box = void 0;
    this._core_geometries = void 0;
    this._core_objects = void 0;
  }
  clone() {
    const core_group = new CoreGroup();
    if (this._objects) {
      const objects = [];
      for (let object of this._objects) {
        objects.push(CoreObject.clone(object));
      }
      core_group.setObjects(objects);
    }
    return core_group;
  }
  setObjects(objects) {
    this._objects = objects;
    this._objects_with_geo = objects.filter((obj) => obj.geometry != null);
    this.touch();
  }
  objects() {
    return this._objects;
  }
  objectsWithGeo() {
    return this._objects_with_geo;
  }
  coreObjects() {
    return this._core_objects = this._core_objects || this._create_core_objects();
  }
  _create_core_objects() {
    if (this._objects) {
      return this._objects.map((object, i) => new CoreObject(object, i));
    }
    return [];
  }
  objectsData() {
    if (this._objects) {
      return this._objects.map((object) => this._objectData(object));
    }
    return [];
  }
  _objectData(object) {
    let points_count = 0;
    if (object.geometry) {
      points_count = CoreGeometry.pointsCount(object.geometry);
    }
    return {
      type: objectTypeFromConstructor(object.constructor),
      name: object.name,
      children_count: object.children.length,
      points_count
    };
  }
  geometries() {
    const list = [];
    for (let core_object of this.coreObjects()) {
      const geometry = core_object.object().geometry;
      if (geometry) {
        list.push(geometry);
      }
    }
    return list;
  }
  coreGeometries() {
    return this._core_geometries = this._core_geometries || this._createCoreGeometries();
  }
  _createCoreGeometries() {
    const list = [];
    for (let geometry of this.geometries()) {
      list.push(new CoreGeometry(geometry));
    }
    return list;
  }
  static geometryFromObject(object) {
    if (object.isMesh || object.isLine || object.isPoints) {
      return object.geometry;
    }
    return null;
  }
  faces() {
    return this.coreGeometries().map((g) => g.faces()).flat();
  }
  points() {
    return this.coreGeometries().map((g) => g.points()).flat();
  }
  pointsCount() {
    return ArrayUtils2.sum(this.coreGeometries().map((g) => g.pointsCount()));
  }
  totalPointsCount() {
    if (this._objects) {
      let sum = 0;
      for (let object of this._objects) {
        object.traverse((object2) => {
          const geometry = object2.geometry;
          if (geometry) {
            sum += CoreGeometry.pointsCount(geometry);
          }
        });
      }
      return sum;
    } else {
      return 0;
    }
  }
  pointsFromGroup(group) {
    if (group) {
      const indices = CoreString.indices(group);
      const points = this.points();
      return ArrayUtils2.compact(indices.map((i) => points[i]));
    } else {
      return this.points();
    }
  }
  static _fromObjects(objects) {
    const core_group = new CoreGroup();
    core_group.setObjects(objects);
    return core_group;
  }
  objectsFromGroup(group_name) {
    return this.coreObjectsFromGroup(group_name).map((co) => co.object());
  }
  coreObjectsFromGroup(group_name) {
    group_name = group_name.trim();
    if (group_name !== "") {
      const index = parseInt(group_name);
      if (!CoreType.isNaN(index)) {
        return ArrayUtils2.compact([this.coreObjects()[index]]);
      } else {
        return this.coreObjects().filter((core_object) => {
          return CoreString.matchMask(group_name, core_object.name());
        });
      }
    } else {
      return this.coreObjects();
    }
  }
  boundingBox() {
    return this._bounding_box = this._bounding_box || this._compute_bounding_box();
  }
  center() {
    const center = new Vector32();
    this.boundingBox().getCenter(center);
    return center;
  }
  size() {
    const size = new Vector32();
    this.boundingBox().getSize(size);
    return size;
  }
  _compute_bounding_box() {
    let bbox;
    if (this._objects) {
      for (let object of this._objects) {
        const geometry = object.geometry;
        if (geometry) {
          geometry.computeBoundingBox();
          if (bbox) {
            bbox.expandByObject(object);
          } else {
            if (geometry.boundingBox) {
              bbox = geometry.boundingBox.clone();
            }
          }
        }
      }
    }
    bbox = bbox || new Box32(new Vector32(-1, -1, -1), new Vector32(1, 1, 1));
    return bbox;
  }
  computeVertexNormals() {
    for (let object of this.coreObjects()) {
      object.computeVertexNormals();
    }
  }
  hasAttrib(name) {
    let first_geometry;
    if ((first_geometry = this.coreGeometries()[0]) != null) {
      return first_geometry.hasAttrib(name);
    } else {
      return false;
    }
  }
  attribType(name) {
    const first_core_geometry = this.coreGeometries()[0];
    if (first_core_geometry != null) {
      return first_core_geometry.attribType(name);
    } else {
      return null;
    }
  }
  objectAttribType(name) {
    const first_core_object = this.coreObjects()[0];
    if (first_core_object != null) {
      return first_core_object.attribType(name);
    } else {
      return null;
    }
  }
  renameAttrib(old_name, new_name, attrib_class) {
    switch (attrib_class) {
      case CoreConstant.ATTRIB_CLASS.VERTEX:
        if (this.hasAttrib(old_name)) {
          if (this._objects) {
            for (let object of this._objects) {
              object.traverse((child) => {
                const geometry = CoreGroup.geometryFromObject(child);
                if (geometry) {
                  const core_geometry = new CoreGeometry(geometry);
                  core_geometry.renameAttrib(old_name, new_name);
                }
              });
            }
          }
        }
        break;
      case CoreConstant.ATTRIB_CLASS.OBJECT:
        if (this.hasAttrib(old_name)) {
          if (this._objects) {
            for (let object of this._objects) {
              object.traverse((child) => {
                const core_object = new CoreObject(child, 0);
                core_object.renameAttrib(old_name, new_name);
              });
            }
          }
        }
        break;
    }
  }
  attribNames() {
    let first_geometry;
    if ((first_geometry = this.coreGeometries()[0]) != null) {
      return first_geometry.attribNames();
    } else {
      return [];
    }
  }
  objectAttribNames() {
    let first_object;
    if ((first_object = this.coreObjects()[0]) != null) {
      return first_object.attribNames();
    } else {
      return [];
    }
  }
  attribNamesMatchingMask(masks_string) {
    const masks = CoreString.attribNames(masks_string);
    const matching_attrib_names = [];
    for (let attrib_name of this.attribNames()) {
      for (let mask of masks) {
        if (CoreString.matchMask(attrib_name, mask)) {
          matching_attrib_names.push(attrib_name);
        }
      }
    }
    return ArrayUtils2.uniq(matching_attrib_names);
  }
  attribSizes() {
    let first_geometry;
    if ((first_geometry = this.coreGeometries()[0]) != null) {
      return first_geometry.attribSizes();
    } else {
      return {};
    }
  }
  objectAttribSizes() {
    let first_object;
    if ((first_object = this.coreObjects()[0]) != null) {
      return first_object.attribSizes();
    } else {
      return {};
    }
  }
  attribSize(attrib_name) {
    let first_geometry;
    if ((first_geometry = this.coreGeometries()[0]) != null) {
      return first_geometry.attribSize(attrib_name);
    } else {
      return 0;
    }
  }
  addNumericVertexAttrib(name, size, default_value) {
    if (default_value == null) {
      default_value = CoreAttribute.default_value(size);
    }
    for (let core_geometry of this.coreGeometries()) {
      core_geometry.addNumericAttrib(name, size, default_value);
    }
  }
  static clone(src_group) {
    const new_group = new Group2();
    src_group.children.forEach((src_object) => {
      const new_object = CoreObject.clone(src_object);
      new_group.add(new_object);
    });
    return new_group;
  }
}
