/*
 * Copyright (C) 2023 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 {StringUtils} from 'common/string_utils';
import {Timestamp, TimestampType} from 'common/time';
import {AbstractParser} from 'parsers/abstract_parser';
import {TraceType} from 'trace/trace_type';
import {PropertyTreeBuilderFromProto} from 'trace/tree_node/property_tree_builder_from_proto';
import {PropertyTreeNode} from 'trace/tree_node/property_tree_node';

class ParserEventLog extends AbstractParser<PropertyTreeNode> {
  private static readonly MAGIC_NUMBER_STRING = 'EventLog';
  private static readonly MAGIC_NUMBER: number[] = Array.from(
    new TextEncoder().encode(ParserEventLog.MAGIC_NUMBER_STRING),
  );

  override getTraceType(): TraceType {
    return TraceType.EVENT_LOG;
  }

  override getMagicNumber(): number[] {
    return ParserEventLog.MAGIC_NUMBER;
  }

  override decodeTrace(buffer: Uint8Array): Event[] {
    const decodedLogs = this.decodeByteArray(buffer);
    const events = this.parseLogs(decodedLogs);
    return events.sort((a: Event, b: Event) => {
      return a.eventTimestamp < b.eventTimestamp ? -1 : 1;
    });
  }

  override getTimestamp(
    type: TimestampType,
    entry: Event,
  ): undefined | Timestamp {
    if (type === TimestampType.REAL) {
      return this.timestampFactory.makeRealTimestamp(entry.eventTimestamp);
    }
    return undefined;
  }

  override processDecodedEntry(
    index: number,
    timestampType: TimestampType,
    entry: Event,
  ): PropertyTreeNode {
    return new PropertyTreeBuilderFromProto()
      .setData(entry)
      .setRootId('EventLogTrace')
      .setRootName('event')
      .build();
  }

  private decodeByteArray(bytes: Uint8Array): string[] {
    const allLogsString = new TextDecoder().decode(bytes);
    const splitLogs = allLogsString.split('\n');

    const firstIndexOfEventLogTrace = splitLogs.findIndex((substring) => {
      return (
        !substring.includes(ParserEventLog.MAGIC_NUMBER_STRING) &&
        !substring.includes('beginning of events') &&
        !StringUtils.isBlank(substring)
      );
    });

    const lastIndexOfEventLogTrace = splitLogs.findIndex((substring, index) => {
      return (
        index > firstIndexOfEventLogTrace && StringUtils.isBlank(substring)
      );
    });

    if (lastIndexOfEventLogTrace === -1) {
      return splitLogs.slice(firstIndexOfEventLogTrace);
    }
    return splitLogs.slice(firstIndexOfEventLogTrace, lastIndexOfEventLogTrace);
  }

  private parseLogs(input: string[]): Event[] {
    return input.map((log) => {
      const [metaData, eventData] = log
        .split(':', 2)
        .map((string) => string.trim());
      const [rawTimestamp, uid, pid, tid, priority, tag] = metaData
        .split(' ')
        .filter((substring) => substring.length > 0);
      const timestampNs = BigInt(rawTimestamp.replace('.', ''));
      return {
        eventTimestamp: timestampNs,
        pid: Number(pid),
        uid: Number(uid),
        tid: Number(tid),
        tag,
        eventData,
      };
    });
  }
}

interface Event {
  eventTimestamp: bigint;
  pid: number;
  uid: number;
  tid: number;
  tag: string;
  eventData: string;
}

export {ParserEventLog};
