<commit_before>import {
  ControlCenter, areEquals, Identifier, Invocation, Result, Aspect, addIsEqualSupport,
  ImmutableMap, ImmutableList, AComponent,
  SafePostLoad, SafePreSave, SafePostSave,
} from './core';
import { Flux } from '@openmicrostep/async';
import { Reporter, Diagnostic, AttributePath } from '@openmicrostep/msbuildsystem.shared';

function diff<T>(type: Aspect.Type, newV: any, oldV: any) : { add: T[], del: T[] } {
  let ret = { add: [] as T[], del: [] as T[] };
  switch (type.type) {
    case 'set':
      if (newV) for (let n of newV)
        if (!oldV || !oldV.has(n))
          ret.add.push(n);
      if (oldV) for (let o of oldV)
        if (!newV || !newV.has(o))
          ret.del.push(o);
      break;
    case 'class':
    case 'or':
      if (oldV !== newV) {
        if (oldV) ret.del.push(oldV);
        if (newV) ret.add.push(newV);
      }
      break;
    default: throw new Error(`unsupported relation type ${type.type}`);
  }

  return ret;
}

export class VersionedObjectManager<T extends VersionedObject = VersionedObject> {
  static UndefinedVersion = -3;
  static NoVersion = -1;
  static DeletedVersion = -2;
  static NextVersion = Number.MAX_SAFE_INTEGER; // 2^56 version should be more than enought
  static SafeMode = true;
  static LocalIdCounter = 0;
  static isLocalId(id: Identifier) {
    return typeof id === "string" && id.startsWith("_localid:");
  }

  /** @internal */ _id: Identifier;
  /** @internal */ _controlCenter: ControlCenter;
  /** @internal */ _aspect: Aspect.Installed;
  /** @internal */ _object: T;
  /** @internal */ _parent_manager: VersionedObjectManager | undefined;
  /** @internal */ _components: Set<object>;

  /** @internal */ _modified_attributes: VersionedObjectManager.Attributes<T>;
  /** @internal */ _saved_version: number;
  /** @internal */ _saved_attributes: VersionedObjectManager.Attributes<T>;
  /** @internal */ _outdated_version: number;
  /** @internal */ _outdated_attributes: VersionedObjectManager.Attributes<T>;

  constructor(controlCenter: ControlCenter, object: T) {
    this._controlCenter = controlCenter;
    this._components = new Set();
    this._id = `_localid:${++VersionedObjectManager.LocalIdCounter}`;
    this._modified_attributes = new Map();
    this._saved_version = VersionedObjectManager.NoVersion;
    this._saved_attributes = new Map();
    this._outdated_version = VersionedObjectManager.NoVersion;
    this._outdated_attributes = new Map();
    this._aspect = (object.constructor as any).aspect;
    this._object = object;
    this._parent_manager = undefined;
  }

  // Environment
  id()     : Identifier { return this._id; }
  version(): number { return this._modified_attributes.size > 0 ? VersionedObjectManager.NextVersion : this._saved_version; }
  object() { return this._object; }
  rootObject() {
    if (!this.isSubObject())
      return this._object;
    else if (this._parent_manager)
      return this._parent_manager.rootObject();
    throw new Error(`cannot find root object of sub object, the sub object is not linked to any parent object`);
  }
  controlCenter() { return this._controlCenter; }
  isRegistered() { return this._components.size > 0; }

  // Definition
  classname() { return this._aspect.classname; }
  aspect() { return this._aspect; }
  isSubObject() { return this._aspect.is_sub_object; }

  // Modified attributes
  isModified(scope?: string[]) : boolean;
  isModified(scope?: (keyof T)[]) : boolean;
  isModified(scope?: (keyof T)[]) {
    if (this._modified_attributes.size === 0)
      return false;
    return scope ? scope.some(attribute => this._modified_attributes.has(attribute)) : true;
  }
  modifiedAttributes(): VersionedObjectManager.ROAttributes<T> { return this._modified_attributes; }

  clear(scope?: string[]) : void;
  clear(scope?: (keyof T)[]) : void;
  clear(scope?: (keyof T)[]) {
    if (!scope)
      this._modified_attributes.clear();
    else
      scope.forEach(attribute => this._modified_attributes.delete(attribute));
  }

  // Saved attributes
  savedVersion(): number { return this._saved_version; }
  savedAttributes(): VersionedObjectManager.ROAttributes<T> { return this._saved_attributes; }

  savedAttributeValue(attribute: string) : any;
  savedAttributeValue<K extends keyof T>(attribute: K) : T[K];
  savedAttributeValue<K extends keyof T>(attribute: K) : T[K] {
    if (this._saved_attributes.has(attribute))
      return this._saved_attributes.get(attribute);
    return this._couldBeMissingValue(attribute);
  }

  // Live attributes
  state(): VersionedObjectManager.State {
    if (this._saved_version === VersionedObjectManager.NoVersion)
      return VersionedObjectManager.State.NEW;
    if (this._saved_version === VersionedObjectManager.DeletedVersion)
      return VersionedObjectManager.State.DELETED;
    if (this._outdated_attributes.size > 0)
      return VersionedObjectManager.State.INCONFLICT;
    if (this._modified_attributes.size > 0)
      return VersionedObjectManager.State.MODIFIED;
    return VersionedObjectManager.State.UNCHANGED;
  }

  attributeState(attribute: string) : VersionedObjectManager.AttributeState;
  attributeState(attribute: keyof T) : VersionedObjectManager.AttributeState;
  attributeState(attribute: keyof T) : VersionedObjectManager.AttributeState {
    if (this._modified_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.MODIFIED;
    if (this._saved_version === VersionedObjectManager.NoVersion)
      return VersionedObjectManager.AttributeState.NEW;
    if (this._saved_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.UNCHANGED;
    if (attribute === '_id' || attribute === '_version')
      return VersionedObjectManager.AttributeState.UNCHANGED;
    if (this._outdated_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.INCONFLICT;
    return VersionedObjectManager.AttributeState.NOTLOADED;
  }

  hasAttributeValue(attributes: string) : boolean;
  hasAttributeValue(attributes: keyof T) : boolean;
  hasAttributeValue(attribute: keyof T) : boolean {
    return attribute === '_id'
        || attribute === '_version'
        || this._modified_attributes.has(attribute)
        || this._saved_attributes.has(attribute)
        || (this._saved_version === VersionedObjectManager.NoVersion && this._aspect.attributes.has(attribute));
  }

  hasEveryAttributesValue(attributes: string[]) : boolean;
  hasEveryAttributesValue(attributes: (keyof T)[]) : boolean;
  hasEveryAttributesValue(attributes: (keyof T)[]) : boolean {
    return attributes.every(attribute => this.hasAttributeValue(attribute));
  }

  attributeValue(attribute: string) : any;
  attributeValue<K extends keyof T>(attribute: K) : T[K];
  attributeValue<K extends keyof T>(attribute: K) : T[K] {
    if (this._modified_attributes.has(attribute))
      return this._modified_attributes.get(attribute);
    if (this._saved_attributes.has(attribute))
      return this._saved_attributes.get(attribute);
    if (attribute === "_id")
      return this.id();
    if (attribute === "_version")
      return this.version();
    if (this._outdated_attributes.has(attribute))
      throw new Error(`attribute '${this.classname()}.${attribute}' is unaccessible due to version change`);
    return this._couldBeMissingValue(attribute);
  }

  validateAttributeValue(attribute: string, value: any): Diagnostic[] {
    let reporter = new Reporter();
    let a = this._aspect.attributes.get(attribute);
    let path = new AttributePath(this.classname(), '{id=', this.id(), '}.', attribute);
    if (!a)
      path.diagnostic(reporter, { is: "error", msg: `attribute doesn't exists` });
    else
      a.validator.validate(reporter, path, value, this);
    return reporter.diagnostics;
  }

  setAttributeValue<K extends keyof T>(attribute: K, value: T[K]) {
    this.setAttributeValueFast(attribute, value, this._attribute_data(attribute));
  }

  setAttributeValueFast<K extends keyof T>(attribute: K, value: T[K], data: Aspect.InstalledAttribute) {
    let hasChanged = false;
    let isNew = this.state() === VersionedObjectManager.State.NEW;
    let oldValue;
    let hasVersionAttribute = this._saved_attributes.has(attribute);
    let hasLocalAttribute = this._modified_attributes.has(attribute);
    if (!hasVersionAttribute && !isNew)
      throw new Error(`attribute '${this.classname()}.${attribute}' is unaccessible and never was`);
    if (hasVersionAttribute && areEquals(this._saved_attributes.get(attribute), value)) {
      if (data.relation || data.contains_vo)
        oldValue = this._modified_attributes.get(attribute);
      hasChanged = this._modified_attributes.delete(attribute);
    }
    else if (!hasLocalAttribute || !areEquals(this._modified_attributes.get(attribute), value)) {
      if (hasLocalAttribute)
        oldValue = this._modified_attributes.get(attribute);
      else if (hasVersionAttribute)
        oldValue = this._saved_attributes.get(attribute);
      this._modified_attributes.set(attribute, value);
      hasChanged = true;
    }
    if (hasChanged && data.contains_vo) {
      let { add, del } = diff<VersionedObject>(data.type, value, oldValue);
      let is_add = true;
      let ai = 0, di = 0;
      while ((is_add = ai < add.length) || di < del.length) {
        let sub_object = is_add ? add[ai++] : del[di++];
        let sub_object_manager = sub_object.manager();
        this._check_sub_object(sub_object_manager, is_add, data);
        if (data.relation) {
          let relation_name = data.relation.attribute.name;
          let relation_type = data.relation.attribute.type;
          if (sub_object_manager.hasAttributeValue(relation_name)) {
            let v = sub_object[relation_name];
            switch (relation_type.type) {
              case 'set':   v = new Set<VersionedObject>(v); v[is_add ? 'add' : 'delete'](this._object); break;
              case 'class': v = is_add ? this._object : undefined; break;
              default: throw new Error(`unsupported relation destination type ${relation_type.type}`);
            }
            sub_object[relation_name] = v;
          }
          else if (is_add && sub_object_manager.state() === VersionedObjectManager.State.NEW) {
            switch (relation_type.type) {
              case 'set':   sub_object[relation_name] = (new Set<VersionedObject>()).add(this._object); break;
              case 'class': sub_object[relation_name] = this._object; break;
              default: throw new Error(`unsupported relation destination type ${relation_type.type}`);
            }
          }
        }
      }
    }
  }

  // Management
  unload(scope?: string[]) : void;
  unload(scope?: (keyof T)[]) : void;
  unload(scope?: (keyof T)[]) {
    if (!scope) {
      this._modified_attributes.clear();
      this._saved_attributes.clear();
      this._outdated_attributes.clear();
    }
    else {
      scope.forEach(attribute => {
        this._modified_attributes.delete(attribute);
        this._saved_attributes.delete(attribute);
        this._outdated_attributes.delete(attribute);
      });
    }
  }

  delete() {
    // TODO: add a shitload of checks
    this._modified_attributes.clear();<commit_msg>core: remove useless chunk of code<commit_after>import {
  ControlCenter, areEquals, Identifier, Invocation, Result, Aspect, addIsEqualSupport,
  ImmutableMap, ImmutableList, AComponent,
  SafePostLoad, SafePreSave, SafePostSave,
} from './core';
import { Flux } from '@openmicrostep/async';
import { Reporter, Diagnostic, AttributePath } from '@openmicrostep/msbuildsystem.shared';

function diff<T>(type: Aspect.Type, newV: any, oldV: any) : { add: T[], del: T[] } {
  let ret = { add: [] as T[], del: [] as T[] };
  switch (type.type) {
    case 'set':
      if (newV) for (let n of newV)
        if (!oldV || !oldV.has(n))
          ret.add.push(n);
      if (oldV) for (let o of oldV)
        if (!newV || !newV.has(o))
          ret.del.push(o);
      break;
    case 'class':
    case 'or':
      if (oldV !== newV) {
        if (oldV) ret.del.push(oldV);
        if (newV) ret.add.push(newV);
      }
      break;
    default: throw new Error(`unsupported relation type ${type.type}`);
  }

  return ret;
}

export class VersionedObjectManager<T extends VersionedObject = VersionedObject> {
  static UndefinedVersion = -3;
  static NoVersion = -1;
  static DeletedVersion = -2;
  static NextVersion = Number.MAX_SAFE_INTEGER; // 2^56 version should be more than enought
  static SafeMode = true;
  static LocalIdCounter = 0;
  static isLocalId(id: Identifier) {
    return typeof id === "string" && id.startsWith("_localid:");
  }

  /** @internal */ _id: Identifier;
  /** @internal */ _controlCenter: ControlCenter;
  /** @internal */ _aspect: Aspect.Installed;
  /** @internal */ _object: T;
  /** @internal */ _parent_manager: VersionedObjectManager | undefined;
  /** @internal */ _components: Set<object>;

  /** @internal */ _modified_attributes: VersionedObjectManager.Attributes<T>;
  /** @internal */ _saved_version: number;
  /** @internal */ _saved_attributes: VersionedObjectManager.Attributes<T>;
  /** @internal */ _outdated_version: number;
  /** @internal */ _outdated_attributes: VersionedObjectManager.Attributes<T>;

  constructor(controlCenter: ControlCenter, object: T) {
    this._controlCenter = controlCenter;
    this._components = new Set();
    this._id = `_localid:${++VersionedObjectManager.LocalIdCounter}`;
    this._modified_attributes = new Map();
    this._saved_version = VersionedObjectManager.NoVersion;
    this._saved_attributes = new Map();
    this._outdated_version = VersionedObjectManager.NoVersion;
    this._outdated_attributes = new Map();
    this._aspect = (object.constructor as any).aspect;
    this._object = object;
    this._parent_manager = undefined;
  }

  // Environment
  id()     : Identifier { return this._id; }
  version(): number { return this._modified_attributes.size > 0 ? VersionedObjectManager.NextVersion : this._saved_version; }
  object() { return this._object; }
  rootObject() {
    if (!this.isSubObject())
      return this._object;
    else if (this._parent_manager)
      return this._parent_manager.rootObject();
    throw new Error(`cannot find root object of sub object, the sub object is not linked to any parent object`);
  }
  controlCenter() { return this._controlCenter; }
  isRegistered() { return this._components.size > 0; }

  // Definition
  classname() { return this._aspect.classname; }
  aspect() { return this._aspect; }
  isSubObject() { return this._aspect.is_sub_object; }

  // Modified attributes
  isModified(scope?: string[]) : boolean;
  isModified(scope?: (keyof T)[]) : boolean;
  isModified(scope?: (keyof T)[]) {
    if (this._modified_attributes.size === 0)
      return false;
    return scope ? scope.some(attribute => this._modified_attributes.has(attribute)) : true;
  }
  modifiedAttributes(): VersionedObjectManager.ROAttributes<T> { return this._modified_attributes; }

  clear(scope?: string[]) : void;
  clear(scope?: (keyof T)[]) : void;
  clear(scope?: (keyof T)[]) {
    if (!scope)
      this._modified_attributes.clear();
    else
      scope.forEach(attribute => this._modified_attributes.delete(attribute));
  }

  // Saved attributes
  savedVersion(): number { return this._saved_version; }
  savedAttributes(): VersionedObjectManager.ROAttributes<T> { return this._saved_attributes; }

  savedAttributeValue(attribute: string) : any;
  savedAttributeValue<K extends keyof T>(attribute: K) : T[K];
  savedAttributeValue<K extends keyof T>(attribute: K) : T[K] {
    if (this._saved_attributes.has(attribute))
      return this._saved_attributes.get(attribute);
    return this._couldBeMissingValue(attribute);
  }

  // Live attributes
  state(): VersionedObjectManager.State {
    if (this._saved_version === VersionedObjectManager.NoVersion)
      return VersionedObjectManager.State.NEW;
    if (this._saved_version === VersionedObjectManager.DeletedVersion)
      return VersionedObjectManager.State.DELETED;
    if (this._outdated_attributes.size > 0)
      return VersionedObjectManager.State.INCONFLICT;
    if (this._modified_attributes.size > 0)
      return VersionedObjectManager.State.MODIFIED;
    return VersionedObjectManager.State.UNCHANGED;
  }

  attributeState(attribute: string) : VersionedObjectManager.AttributeState;
  attributeState(attribute: keyof T) : VersionedObjectManager.AttributeState;
  attributeState(attribute: keyof T) : VersionedObjectManager.AttributeState {
    if (this._modified_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.MODIFIED;
    if (this._saved_version === VersionedObjectManager.NoVersion)
      return VersionedObjectManager.AttributeState.NEW;
    if (this._saved_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.UNCHANGED;
    if (attribute === '_id' || attribute === '_version')
      return VersionedObjectManager.AttributeState.UNCHANGED;
    if (this._outdated_attributes.has(attribute))
      return VersionedObjectManager.AttributeState.INCONFLICT;
    return VersionedObjectManager.AttributeState.NOTLOADED;
  }

  hasAttributeValue(attributes: string) : boolean;
  hasAttributeValue(attributes: keyof T) : boolean;
  hasAttributeValue(attribute: keyof T) : boolean {
    return attribute === '_id'
        || attribute === '_version'
        || this._modified_attributes.has(attribute)
        || this._saved_attributes.has(attribute)
        || (this._saved_version === VersionedObjectManager.NoVersion && this._aspect.attributes.has(attribute));
  }

  hasEveryAttributesValue(attributes: string[]) : boolean;
  hasEveryAttributesValue(attributes: (keyof T)[]) : boolean;
  hasEveryAttributesValue(attributes: (keyof T)[]) : boolean {
    return attributes.every(attribute => this.hasAttributeValue(attribute));
  }

  attributeValue(attribute: string) : any;
  attributeValue<K extends keyof T>(attribute: K) : T[K];
  attributeValue<K extends keyof T>(attribute: K) : T[K] {
    if (this._modified_attributes.has(attribute))
      return this._modified_attributes.get(attribute);
    if (this._saved_attributes.has(attribute))
      return this._saved_attributes.get(attribute);
    if (attribute === "_id")
      return this.id();
    if (attribute === "_version")
      return this.version();
    if (this._outdated_attributes.has(attribute))
      throw new Error(`attribute '${this.classname()}.${attribute}' is unaccessible due to version change`);
    return this._couldBeMissingValue(attribute);
  }

  validateAttributeValue(attribute: string, value: any): Diagnostic[] {
    let reporter = new Reporter();
    let a = this._aspect.attributes.get(attribute);
    let path = new AttributePath(this.classname(), '{id=', this.id(), '}.', attribute);
    if (!a)
      path.diagnostic(reporter, { is: "error", msg: `attribute doesn't exists` });
    else
      a.validator.validate(reporter, path, value, this);
    return reporter.diagnostics;
  }

  setAttributeValue<K extends keyof T>(attribute: K, value: T[K]) {
    this.setAttributeValueFast(attribute, value, this._attribute_data(attribute));
  }

  setAttributeValueFast<K extends keyof T>(attribute: K, value: T[K], data: Aspect.InstalledAttribute) {
    let hasChanged = false;
    let isNew = this.state() === VersionedObjectManager.State.NEW;
    let oldValue;
    let hasVersionAttribute = this._saved_attributes.has(attribute);
    let hasLocalAttribute = this._modified_attributes.has(attribute);
    if (!hasVersionAttribute && !isNew)
      throw new Error(`attribute '${this.classname()}.${attribute}' is unaccessible and never was`);
    if (hasVersionAttribute && areEquals(this._saved_attributes.get(attribute), value)) {
      if (data.relation || data.contains_vo)
        oldValue = this._modified_attributes.get(attribute);
      hasChanged = this._modified_attributes.delete(attribute);
    }
    else if (!hasLocalAttribute || !areEquals(this._modified_attributes.get(attribute), value)) {
      if (hasLocalAttribute)
        oldValue = this._modified_attributes.get(attribute);
      else if (hasVersionAttribute)
        oldValue = this._saved_attributes.get(attribute);
      this._modified_attributes.set(attribute, value);
      hasChanged = true;
    }
    if (hasChanged && data.contains_vo) {
      let { add, del } = diff<VersionedObject>(data.type, value, oldValue);
      let is_add = true;
      let ai = 0, di = 0;
      while ((is_add = ai < add.length) || di < del.length) {
        let sub_object = is_add ? add[ai++] : del[di++];
        let sub_object_manager = sub_object.manager();
        this._check_sub_object(sub_object_manager, is_add, data);
        if (data.relation) {
          let relation_name = data.relation.attribute.name;
          let relation_type = data.relation.attribute.type;
          if (sub_object_manager.hasAttributeValue(relation_name)) {
            let v = sub_object[relation_name];
            switch (relation_type.type) {
              case 'set':   v = new Set<VersionedObject>(v); v[is_add ? 'add' : 'delete'](this._object); break;
              case 'class': v = is_add ? this._object : undefined; break;
              default: throw new Error(`unsupported relation destination type ${relation_type.type}`);
            }
            sub_object[relation_name] = v;
          }
        }
      }
    }
  }

  // Management
  unload(scope?: string[]) : void;
  unload(scope?: (keyof T)[]) : void;
  unload(scope?: (keyof T)[]) {
    if (!scope) {
      this._modified_attributes.clear();
      this._saved_attributes.clear();
      this._outdated_attributes.clear();
    }
    else {
      scope.forEach(attribute => {
        this._modified_attributes.delete(attribute);
        this._saved_attributes.delete(attribute);
        this._outdated_attributes.delete(attribute);
      });
    }
  }

  delete() {
    // TODO: add a shitload of checks
    this._modified_attributes.clear();
{'fraction_non_alphanumeric': 0.12776111262612488, 'fraction_numerical': 0.0012726115807653849, 'mean_word_length': 3.752267818574514, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '917065', 'n_tokens_mistral': 6439, 'n_tokens_neox': 6381, 'n_words': 1416}