// 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 {Engine} from '../../trace_processor/engine';
import {Row} from '../../trace_processor/query_result';

export interface QueryResponse {
  query: string;
  error?: string;
  totalRowCount: number;
  durationMs: number;
  columns: string[];
  rows: Row[];
  statementCount: number;
  statementWithOutputCount: number;
  lastStatementSql: string;
}

/**
 * Runs a query and pulls out all the columns into a list of 'row' objects,
 * where each row contains a dictionary of [columnName] -> value.
 *
 * This method will not throw if the query fails, instead the error is populated
 * in the return value.
 *
 * This function is designed to be used with table viewers, where the structure
 * of resulting rows is not known up front. Use engine.query() or
 * engine.tryQuery() and use the iterators if the resulting data is to be
 * processed.
 *
 * @param sqlQuery - The query to evaluate.
 * @param engine - The engine to use to run the query.
 */
export async function runQueryForQueryTable(
  sqlQuery: string,
  engine: Engine,
): Promise<QueryResponse> {
  const startMs = performance.now();

  // TODO(primiano): once the controller thread is gone we should pass down
  // the result objects directly to the frontend, iterate over the result
  // and deal with pagination there. For now we keep the old behavior and
  // truncate to 10k rows.

  const maybeResult = await engine.tryQuery(sqlQuery);

  if (maybeResult.ok) {
    const queryRes = maybeResult.value;

    const rows: Row[] = [];
    const columns = queryRes.columns();
    for (const iter = queryRes.iter({}); iter.valid(); iter.next()) {
      const row: Row = {};
      for (const colName of columns) {
        const value = iter.get(colName);
        row[colName] = value;
      }
      rows.push(row);
    }

    const result: QueryResponse = {
      query: sqlQuery,
      durationMs: queryRes.elapsedTimeMs(),
      error: queryRes.error(),
      totalRowCount: queryRes.numRows(),
      columns,
      rows,
      statementCount: queryRes.statementCount(),
      statementWithOutputCount: queryRes.statementWithOutputCount(),
      lastStatementSql: queryRes.lastStatementSql(),
    };
    return result;
  } else {
    // In the case of a query error we don't want the exception to bubble up
    // as a crash. The |queryRes| object will be populated anyways.
    // queryRes.error() is used to tell if the query errored or not. If it
    // errored, the frontend will show a graceful message instead.
    return {
      query: sqlQuery,
      durationMs: performance.now() - startMs,
      error: maybeResult.error,
      totalRowCount: 0,
      columns: [],
      rows: [],
      statementCount: 0,
      statementWithOutputCount: 0,
      lastStatementSql: '',
    };
  }
}
