// Copyright (C) 2018 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {assertFalse, assertTrue} from '../base/logging';
import {TimeSpan} from '../common/time';

const MAX_ZOOM_SPAN_SEC = 1e-8;  // 10 ns.

/**
 * Defines a mapping between number and seconds for the entire application.
 * Linearly scales time values from boundsMs to pixel values in boundsPx and
 * back.
 */
export class TimeScale {
  private timeBounds: TimeSpan;
  private _startPx: number;
  private _endPx: number;
  private secPerPx = 0;

  constructor(timeBounds: TimeSpan, boundsPx: [number, number]) {
    this.timeBounds = timeBounds;
    this._startPx = boundsPx[0];
    this._endPx = boundsPx[1];
    this.updateSlope();
  }

  private updateSlope() {
    this.secPerPx = this.timeBounds.duration / (this._endPx - this._startPx);
  }

  deltaTimeToPx(time: number): number {
    return Math.round(time / this.secPerPx);
  }

  timeToPx(time: number): number {
    return this._startPx + (time - this.timeBounds.start) / this.secPerPx;
  }

  pxToTime(px: number): number {
    return this.timeBounds.start + (px - this._startPx) * this.secPerPx;
  }

  deltaPxToDuration(px: number): number {
    return px * this.secPerPx;
  }

  setTimeBounds(timeBounds: TimeSpan) {
    this.timeBounds = timeBounds;
    this.updateSlope();
  }

  setLimitsPx(pxStart: number, pxEnd: number) {
    assertFalse(pxStart === pxEnd);
    assertTrue(pxStart >= 0 && pxEnd >= 0);
    this._startPx = pxStart;
    this._endPx = pxEnd;
    this.updateSlope();
  }

  timeInBounds(time: number): boolean {
    return this.timeBounds.isInBounds(time);
  }

  get startPx(): number {
    return this._startPx;
  }

  get endPx(): number {
    return this._endPx;
  }
}

export function computeZoom(
    scale: TimeScale, span: TimeSpan, zoomFactor: number, zoomPx: number):
    TimeSpan {
  const startPx = scale.startPx;
  const endPx = scale.endPx;
  const deltaPx = endPx - startPx;
  const deltaTime = span.end - span.start;
  const newDeltaTime = Math.max(deltaTime * zoomFactor, MAX_ZOOM_SPAN_SEC);
  const clampedZoomPx = Math.max(startPx, Math.min(endPx, zoomPx));
  const zoomTime = scale.pxToTime(clampedZoomPx);
  const r = (clampedZoomPx - startPx) / deltaPx;
  const newStartTime = zoomTime - newDeltaTime * r;
  const newEndTime = newStartTime + newDeltaTime;
  return new TimeSpan(newStartTime, newEndTime);
}
