import {Color as Color2} from "three/src/math/Color";
import {CoreGeometry} from "./Geometry";
import {CoreAttribute} from "./Attribute";
import {CoreConstant, AttribType} from "./Constant";
import {CoreMaterial} from "./Material";
import {CoreString} from "../String";
import {CoreEntity} from "./Entity";
import {CoreType} from "../Type";
import {ObjectUtils as ObjectUtils2} from "../ObjectUtils";
const PTNUM = "ptnum";
const NAME_ATTR = "name";
const ATTRIBUTES = "attributes";
export class CoreObject extends CoreEntity {
  constructor(_object, index) {
    super(index);
    this._object = _object;
    if (this._object.userData[ATTRIBUTES] == null) {
      this._object.userData[ATTRIBUTES] = {};
    }
  }
  object() {
    return this._object;
  }
  geometry() {
    return this._object.geometry;
  }
  coreGeometry() {
    const geo = this.geometry();
    if (geo) {
      return new CoreGeometry(geo);
    } else {
      return null;
    }
  }
  points() {
    return this.coreGeometry()?.points() || [];
  }
  pointsFromGroup(group) {
    if (group) {
      const indices = CoreString.indices(group);
      if (indices) {
        const points = this.points();
        return indices.map((i) => points[i]);
      } else {
        return [];
      }
    } else {
      return this.points();
    }
  }
  static isInGroup(groupString, object) {
    const group = groupString.trim();
    if (group.length == 0) {
      return true;
    }
    const elements = group.split("=");
    const attribNameWithPrefix = elements[0];
    if (attribNameWithPrefix[0] == "@") {
      const attribName = attribNameWithPrefix.substr(1);
      const expectedAttribValue = elements[1];
      const currentAttribValue = this.attribValue(object, attribName);
      return expectedAttribValue == currentAttribValue;
    }
    return false;
  }
  computeVertexNormals() {
    this.coreGeometry()?.computeVertexNormals();
  }
  static addAttribute(object, attrib_name, value) {
    let data;
    if (value != null && !CoreType.isNumber(value) && !CoreType.isArray(value) && !CoreType.isString(value)) {
      data = value.toArray();
    } else {
      data = value;
    }
    const user_data = object.userData;
    user_data[ATTRIBUTES] = user_data[ATTRIBUTES] || {};
    user_data[ATTRIBUTES][attrib_name] = data;
  }
  addAttribute(name, value) {
    CoreObject.addAttribute(this._object, name, value);
  }
  addNumericAttrib(name, value) {
    this.addAttribute(name, value);
  }
  setAttribValue(name, value) {
    this.addAttribute(name, value);
  }
  addNumericVertexAttrib(name, size, default_value) {
    if (default_value == null) {
      default_value = CoreAttribute.default_value(size);
    }
    this.coreGeometry()?.addNumericAttrib(name, size, default_value);
  }
  attributeNames() {
    return Object.keys(this._object.userData[ATTRIBUTES]);
  }
  attribNames() {
    return this.attributeNames();
  }
  hasAttrib(name) {
    return this.attributeNames().includes(name);
  }
  renameAttrib(old_name, new_name) {
    const current_value = this.attribValue(old_name);
    if (current_value != null) {
      this.addAttribute(new_name, current_value);
      this.deleteAttribute(old_name);
    } else {
      console.warn(`attribute ${old_name} not found`);
    }
  }
  deleteAttribute(name) {
    delete this._object.userData[ATTRIBUTES][name];
  }
  static attribValue(object, name, index = 0, target) {
    if (name === PTNUM) {
      return index;
    }
    if (object.userData && object.userData[ATTRIBUTES]) {
      const val = object.userData[ATTRIBUTES][name];
      if (val == null) {
        if (name == NAME_ATTR) {
          return object.name;
        }
      } else {
        if (CoreType.isArray(val) && target) {
          target.fromArray(val);
          return target;
        }
      }
      return val;
    }
    if (name == NAME_ATTR) {
      return object.name;
    }
  }
  static stringAttribValue(object, name, index = 0) {
    const str = this.attribValue(object, name, index);
    if (str != null) {
      if (CoreType.isString(str)) {
        return str;
      } else {
        return `${str}`;
      }
    }
  }
  attribValue(name, target) {
    return CoreObject.attribValue(this._object, name, this._index, target);
  }
  stringAttribValue(name) {
    return CoreObject.stringAttribValue(this._object, name, this._index);
  }
  name() {
    return this.attribValue(NAME_ATTR);
  }
  humanType() {
    return CoreConstant.CONSTRUCTOR_NAMES_BY_CONSTRUCTOR_NAME[this._object.constructor.name];
  }
  attribTypes() {
    const h = {};
    for (let attrib_name of this.attribNames()) {
      const type = this.attribType(attrib_name);
      if (type != null) {
        h[attrib_name] = type;
      }
    }
    return h;
  }
  attribType(name) {
    const val = this.attribValue(name);
    if (CoreType.isString(val)) {
      return AttribType.STRING;
    } else {
      return AttribType.NUMERIC;
    }
  }
  attribSizes() {
    const h = {};
    for (let attrib_name of this.attribNames()) {
      const size = this.attribSize(attrib_name);
      if (size != null) {
        h[attrib_name] = size;
      }
    }
    return h;
  }
  attribSize(name) {
    const val = this.attribValue(name);
    if (val == null) {
      return null;
    }
    return CoreAttribute.attribSizeFromValue(val);
  }
  clone() {
    return CoreObject.clone(this._object);
  }
  static clone(src_object) {
    const new_object = src_object.clone();
    var sourceLookup = new Map();
    var cloneLookup = new Map();
    CoreObject.parallelTraverse(src_object, new_object, function(sourceNode, clonedNode) {
      sourceLookup.set(clonedNode, sourceNode);
      cloneLookup.set(sourceNode, clonedNode);
    });
    new_object.traverse(function(node) {
      const src_node = sourceLookup.get(node);
      const mesh_node = node;
      if (mesh_node.geometry) {
        const src_node_geometry = src_node.geometry;
        mesh_node.geometry = CoreGeometry.clone(src_node_geometry);
        const mesh_node_geometry = mesh_node.geometry;
        if (mesh_node_geometry.userData) {
          mesh_node_geometry.userData = ObjectUtils2.cloneDeep(src_node_geometry.userData);
        }
      }
      if (mesh_node.material) {
        mesh_node.material = src_node.material;
        CoreMaterial.apply_custom_materials(node, mesh_node.material);
        const material_with_color = mesh_node.material;
        if (material_with_color.color == null) {
          material_with_color.color = new Color2(1, 1, 1);
        }
      }
      if (src_object.userData) {
        node.userData = ObjectUtils2.cloneDeep(src_node.userData);
      }
      const src_node_with_animations = src_node;
      if (src_node_with_animations.animations) {
        node.animations = src_node_with_animations.animations.map((animation) => animation.clone());
      }
      const skinned_node = node;
      if (skinned_node.isSkinnedMesh) {
        var clonedMesh = skinned_node;
        var sourceMesh = src_node;
        var sourceBones = sourceMesh.skeleton.bones;
        clonedMesh.skeleton = sourceMesh.skeleton.clone();
        clonedMesh.bindMatrix.copy(sourceMesh.bindMatrix);
        const new_bones = sourceBones.map(function(bone) {
          return cloneLookup.get(bone);
        });
        clonedMesh.skeleton.bones = new_bones;
        clonedMesh.bind(clonedMesh.skeleton, clonedMesh.bindMatrix);
      }
    });
    return new_object;
  }
  static parallelTraverse(a, b, callback) {
    callback(a, b);
    for (var i = 0; i < a.children.length; i++) {
      this.parallelTraverse(a.children[i], b.children[i], callback);
    }
  }
}
