// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export interface Progress {
  totalWork: number;
  worked: number;
  title?: string;
  canceled: boolean;
  done: boolean;
}

export class Progress implements Progress {
  totalWork = 0;
  worked = 0;
  title?: string;
  canceled = false;
  done = false;
}

export class CompositeProgress {
  readonly parent: Progress;
  readonly #children: SubProgress[];
  #childrenDone: number;

  constructor(parent: Progress) {
    this.parent = parent;
    this.#children = [];
    this.#childrenDone = 0;
    this.parent.totalWork = 1;
    this.parent.worked = 0;
  }

  childDone(): void {
    if (++this.#childrenDone !== this.#children.length) {
      return;
    }
    this.parent.done = true;
  }

  createSubProgress(weight?: number): SubProgress {
    const child = new SubProgress(this, weight);
    this.#children.push(child);
    return child;
  }

  update(): void {
    let totalWeights = 0;
    let done = 0;

    for (let i = 0; i < this.#children.length; ++i) {
      const child = this.#children[i];
      if (child.totalWork) {
        done += child.weight * child.worked / child.totalWork;
      }
      totalWeights += child.weight;
    }
    this.parent.worked = done / totalWeights;
  }
}

export class SubProgress implements Progress {
  readonly #composite: CompositeProgress;
  #weight: number;
  #worked: number;
  #totalWork: number;
  constructor(composite: CompositeProgress, weight?: number) {
    this.#composite = composite;
    this.#weight = weight || 1;
    this.#worked = 0;

    this.#totalWork = 0;
  }

  get canceled(): boolean {
    return this.#composite.parent.canceled;
  }

  set title(title: string) {
    this.#composite.parent.title = title;
  }

  set done(done: boolean) {
    if (!done) {
      return;
    }
    this.worked = this.#totalWork;
    this.#composite.childDone();
  }

  set totalWork(totalWork: number) {
    this.#totalWork = totalWork;
    this.#composite.update();
  }

  set worked(worked: number) {
    this.#worked = worked;
    this.#composite.update();
  }

  get weight(): number {
    return this.#weight;
  }

  get worked(): number {
    return this.#worked;
  }

  get totalWork(): number {
    return this.#totalWork;
  }
}

export class ProgressProxy implements Progress {
  readonly #delegate?: Progress|null;
  readonly #doneCallback?: (() => void);
  readonly #updateCallback?: (() => void);

  constructor(delegate?: Progress|null, doneCallback?: (() => void), updateCallback?: (() => void)) {
    this.#delegate = delegate;
    this.#doneCallback = doneCallback;
    this.#updateCallback = updateCallback;
  }

  get canceled(): boolean {
    return this.#delegate ? this.#delegate.canceled : false;
  }

  set title(title: string) {
    if (this.#delegate) {
      this.#delegate.title = title;
    }
    if (this.#updateCallback) {
      this.#updateCallback();
    }
  }

  get title(): string {
    return this.#delegate?.title ?? '';
  }

  set done(done: boolean) {
    if (this.#delegate) {
      this.#delegate.done = done;
    }
    if (done && this.#doneCallback) {
      this.#doneCallback();
    }
  }

  get done(): boolean {
    return this.#delegate ? this.#delegate.done : false;
  }

  set totalWork(totalWork: number) {
    if (this.#delegate) {
      this.#delegate.totalWork = totalWork;
    }
    if (this.#updateCallback) {
      this.#updateCallback();
    }
  }

  get totalWork(): number {
    return this.#delegate ? this.#delegate.totalWork : 0;
  }

  set worked(worked: number) {
    if (this.#delegate) {
      this.#delegate.worked = worked;
    }
    if (this.#updateCallback) {
      this.#updateCallback?.();
    }
  }

  get worked(): number {
    return this.#delegate ? this.#delegate.worked : 0;
  }
}
