/*
 * Copyright (C) 2024 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 {Timestamp} from 'common/time';
import {TimeUtils} from 'common/time_utils';
import {RawDataUtils} from 'parsers/raw_data_utils';
import {TransformUtils} from 'parsers/surface_flinger/transform_utils';
import {PropertyTreeNode} from './property_tree_node';

const EMPTY_OBJ_STRING = '{empty}';
const EMPTY_ARRAY_STRING = '[empty]';

function formatNumber(value: number): string {
  if (!Number.isInteger(value)) {
    return value.toFixed(3).toString();
  }
  return value.toString();
}

interface PropertyFormatter {
  format(node: PropertyTreeNode): string;
}

class DefaultPropertyFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const value = node.getValue();
    if (Array.isArray(value) && value.length === 0) {
      return EMPTY_ARRAY_STRING;
    }

    if (typeof value === 'number') {
      return formatNumber(value);
    }

    if (value?.toString) return value.toString();

    return `${value}`;
  }
}
const DEFAULT_PROPERTY_FORMATTER = new DefaultPropertyFormatter();

class ColorFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const rNode = node.getChildByName('r');
    const gNode = node.getChildByName('g');
    const bNode = node.getChildByName('b');
    const alphaNode = node.getChildByName('a');

    const r = formatNumber(rNode?.getValue() ?? 0);
    const g = formatNumber(gNode?.getValue() ?? 0);
    const b = formatNumber(bNode?.getValue() ?? 0);
    if (rNode && gNode && bNode && !alphaNode) {
      return `(${r}, ${g}, ${b})`;
    }

    const alpha = formatNumber(alphaNode?.getValue() ?? 0);
    if (RawDataUtils.isEmptyObj(node)) {
      return `${EMPTY_OBJ_STRING}, alpha: ${alpha}`;
    }
    return `(${r}, ${g}, ${b}, ${alpha})`;
  }
}
const COLOR_FORMATTER = new ColorFormatter();

class RectFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    if (!RawDataUtils.isRect(node) || RawDataUtils.isEmptyObj(node)) {
      return EMPTY_OBJ_STRING;
    }
    const left = formatNumber(node.getChildByName('left')?.getValue() ?? 0);
    const top = formatNumber(node.getChildByName('top')?.getValue() ?? 0);
    const right = formatNumber(node.getChildByName('right')?.getValue() ?? 0);
    const bottom = formatNumber(node.getChildByName('bottom')?.getValue() ?? 0);

    return `(${left}, ${top}) - (${right}, ${bottom})`;
  }
}
const RECT_FORMATTER = new RectFormatter();

class BufferFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    return `w: ${node.getChildByName('width')?.getValue() ?? 0}, h: ${
      node.getChildByName('height')?.getValue() ?? 0
    }, stride: ${node.getChildByName('stride')?.getValue()}, format: ${node
      .getChildByName('format')
      ?.getValue()}`;
  }
}
const BUFFER_FORMATTER = new BufferFormatter();

class LayerIdFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const value = node.getValue();
    return value === -1 || value === 0 ? 'none' : `${value}`;
  }
}
const LAYER_ID_FORMATTER = new LayerIdFormatter();

class MatrixFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const dsdx = formatNumber(node.getChildByName('dsdx')?.getValue() ?? 0);
    const dtdx = formatNumber(node.getChildByName('dtdx')?.getValue() ?? 0);
    const dsdy = formatNumber(node.getChildByName('dsdy')?.getValue() ?? 0);
    const dtdy = formatNumber(node.getChildByName('dtdy')?.getValue() ?? 0);
    const tx = node.getChildByName('tx');
    const ty = node.getChildByName('ty');
    if (
      dsdx === '0' &&
      dtdx === '0' &&
      dsdy === '0' &&
      dtdy === '0' &&
      !tx &&
      !ty
    ) {
      return 'null';
    }
    const matrix22 = `dsdx: ${dsdx}, dtdx: ${dtdx}, dsdy: ${dsdy}, dtdy: ${dtdy}`;
    if (!tx && !ty) {
      return matrix22;
    }
    return (
      matrix22 +
      `, tx: ${formatNumber(tx?.getValue() ?? 0)}, ty: ${formatNumber(
        ty?.getValue() ?? 0,
      )}`
    );
  }
}
const MATRIX_FORMATTER = new MatrixFormatter();

class TransformFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const type = node.getChildByName('type');
    return type !== undefined
      ? TransformUtils.getTypeFlags(type.getValue() ?? 0)
      : 'null';
  }
}
const TRANSFORM_FORMATTER = new TransformFormatter();

class SizeFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    return `${node.getChildByName('w')?.getValue() ?? 0} x ${
      node.getChildByName('h')?.getValue() ?? 0
    }`;
  }
}
const SIZE_FORMATTER = new SizeFormatter();

class PositionFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const x = formatNumber(node.getChildByName('x')?.getValue() ?? 0);
    const y = formatNumber(node.getChildByName('y')?.getValue() ?? 0);
    return `x: ${x}, y: ${y}`;
  }
}
const POSITION_FORMATTER = new PositionFormatter();

class RegionFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    let res = 'SkRegion(';
    node
      .getChildByName('rect')
      ?.getAllChildren()
      .forEach((rectNode: PropertyTreeNode) => {
        res += `(${rectNode.getChildByName('left')?.getValue() ?? 0}, ${
          rectNode.getChildByName('top')?.getValue() ?? 0
        }, ${rectNode.getChildByName('right')?.getValue() ?? 0}, ${
          rectNode.getChildByName('bottom')?.getValue() ?? 0
        })`;
      });
    return res + ')';
  }
}
const REGION_FORMATTER = new RegionFormatter();

class EnumFormatter implements PropertyFormatter {
  constructor(private readonly valuesById: {[key: number]: string}) {}

  format(node: PropertyTreeNode): string {
    const value = node.getValue();
    if (typeof value === 'number' && this.valuesById[value]) {
      return this.valuesById[value];
    }
    if (typeof value === 'bigint' && this.valuesById[Number(value)]) {
      return this.valuesById[Number(value)];
    }
    return `${value}`;
  }
}

class FixedStringFormatter implements PropertyFormatter {
  constructor(private readonly fixedStringValue: string) {}

  format(node: PropertyTreeNode): string {
    return this.fixedStringValue;
  }
}

class TimestampFormatter implements PropertyFormatter {
  format(node: PropertyTreeNode): string {
    const timestamp = node.getValue();
    if (timestamp instanceof Timestamp) {
      return TimeUtils.format(timestamp);
    }
    return 'null';
  }
}
const TIMESTAMP_FORMATTER = new TimestampFormatter();

export {
  EMPTY_OBJ_STRING,
  EMPTY_ARRAY_STRING,
  PropertyFormatter,
  DEFAULT_PROPERTY_FORMATTER,
  COLOR_FORMATTER,
  RECT_FORMATTER,
  BUFFER_FORMATTER,
  LAYER_ID_FORMATTER,
  TRANSFORM_FORMATTER,
  SIZE_FORMATTER,
  POSITION_FORMATTER,
  REGION_FORMATTER,
  EnumFormatter,
  FixedStringFormatter,
  TIMESTAMP_FORMATTER,
  MATRIX_FORMATTER,
};
