// Copyright 2022 The Pigweed Authors
//
// 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
//
//     https://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.

// eslint-disable-next-line no-undef
const { createLogViewer, LogSource, LogEntry, Severity } = PigweedLogging;

let currentTheme = {};
let defaultLogStyleRule = 'color: #ffffff;';
let columnStyleRules = {};
let defaultColumnStyles = [];
let logLevelStyles = {};

const logLevelToString = {
  10: 'DBG',
  20: 'INF',
  21: 'OUT',
  30: 'WRN',
  40: 'ERR',
  50: 'CRT',
  70: 'FTL',
};

const logLevelToSeverity = {
  10: Severity.DEBUG,
  20: Severity.INFO,
  21: Severity.INFO,
  30: Severity.WARNING,
  40: Severity.ERROR,
  50: Severity.CRITICAL,
  70: Severity.CRITICAL,
};

let nonAdditionalDataFields = [
  '_hosttime',
  'levelname',
  'levelno',
  'args',
  'fields',
  'message',
  'time',
];
let additionalHeaders = [];

// New LogSource to consume pw-console log json messages
class PwConsoleLogSource extends LogSource {
  constructor() {
    super();
  }
  append_log(data) {
    var fields = [
      { key: 'severity', value: logLevelToSeverity[data.levelno] },
      { key: 'time', value: data.time },
    ];
    Object.keys(data.fields).forEach((columnName) => {
      if (
        nonAdditionalDataFields.indexOf(columnName) === -1 &&
        additionalHeaders.indexOf(columnName) === -1
      ) {
        fields.push({ key: columnName, value: data.fields[columnName] });
      }
    });
    fields.push({ key: 'message', value: data.message });
    fields.push({ key: 'py_file', value: data.py_file });
    fields.push({ key: 'py_logger', value: data.py_logger });
    this.publishLogEntry({
      severity: logLevelToSeverity[data.levelno],
      timestamp: new Date(),
      fields: fields,
    });
  }
}

// Setup the pigweedjs log-viewer
const logSource = new PwConsoleLogSource();
const containerEl = document.querySelector('#log-viewer-container');
let unsubscribe = createLogViewer(logSource, containerEl);

// Format a date in the standard pw_cli style YYYY-mm-dd HH:MM:SS
function formatDate(dt) {
  function pad2(n) {
    return (n < 10 ? '0' : '') + n;
  }

  return (
    dt.getFullYear() +
    pad2(dt.getMonth() + 1) +
    pad2(dt.getDate()) +
    ' ' +
    pad2(dt.getHours()) +
    ':' +
    pad2(dt.getMinutes()) +
    ':' +
    pad2(dt.getSeconds())
  );
}

// Return the value for the given # parameter name.
function getUrlHashParameter(param) {
  var params = getUrlHashParameters();
  return params[param];
}

// Capture all # parameters from the current URL.
function getUrlHashParameters() {
  var sPageURL = window.location.hash;
  if (sPageURL) sPageURL = sPageURL.split('#')[1];
  var pairs = sPageURL.split('&');
  var object = {};
  pairs.forEach(function (pair, i) {
    pair = pair.split('=');
    if (pair[0] !== '') object[pair[0]] = pair[1];
  });
  return object;
}

// Update web page CSS styles based on a pw-console color json log message.
function setCurrentTheme(newTheme) {
  currentTheme = newTheme;
  defaultLogStyleRule = parsePromptToolkitStyle(newTheme.default);
  // Set body background color
  // document.querySelector('body').setAttribute('style', defaultLogStyleRule);

  // Apply default font styles to columns
  let styles = [];
  Object.keys(newTheme).forEach((key) => {
    if (key.startsWith('log-table-column-')) {
      styles.push(newTheme[key]);
    }
    if (key.startsWith('log-level-')) {
      logLevelStyles[parseInt(key.replace('log-level-', ''))] =
        parsePromptToolkitStyle(newTheme[key]);
    }
  });
  defaultColumnStyles = styles;
}

// Convert prompt_toolkit color format strings to CSS.
// 'bg:#BG-HEX #FG-HEX STYLE' where STYLE is either 'bold' or 'underline'
function parsePromptToolkitStyle(rule) {
  const ruleList = rule.split(' ');
  let outputStyle = ruleList.map((fragment) => {
    if (fragment.startsWith('bg:')) {
      return `background-color: ${fragment.replace('bg:', '')}`;
    } else if (fragment === 'bold') {
      return `font-weight: bold`;
    } else if (fragment === 'underline') {
      return `text-decoration: underline`;
    } else if (fragment.startsWith('#')) {
      return `color: ${fragment}`;
    }
  });
  return outputStyle.join(';');
}

// Inject styled spans into the log message column values.
function applyStyling(data, applyColors = false) {
  let colIndex = 0;
  Object.keys(data).forEach((key) => {
    if (columnStyleRules[key] && typeof data[key] === 'string') {
      Object.keys(columnStyleRules[key]).forEach((token) => {
        data[key] = data[key].replaceAll(
          token,
          `<span
              style="${defaultLogStyleRule};${
                applyColors
                  ? defaultColumnStyles[colIndex % defaultColumnStyles.length]
                  : ''
              };${parsePromptToolkitStyle(columnStyleRules[key][token])};">
                ${token}
            </span>`,
        );
      });
    } else if (key === 'fields') {
      data[key] = applyStyling(data.fields, true);
    }
    if (applyColors) {
      data[key] = `<span
      style="${parsePromptToolkitStyle(
        defaultColumnStyles[colIndex % defaultColumnStyles.length],
      )}">
        ${data[key]}
      </span>`;
    }
    colIndex++;
  });
  return data;
}

// Connect to the pw-console websocket and start emitting logs.
(function () {
  const container = document.querySelector('.log-container');
  const height = window.innerHeight - 50;
  let follow = true;

  const port = getUrlHashParameter('ws');
  const hostname = location.hostname || '127.0.0.1';
  var ws = new WebSocket(`ws://${hostname}:${port}/`);
  ws.onmessage = function (event) {
    let dataObj;
    try {
      dataObj = JSON.parse(event.data);
    } catch (e) {
      // empty
    }
    if (!dataObj) return;

    if (dataObj.__pw_console_colors) {
      // If this is a color theme message, update themes.
      const colors = dataObj.__pw_console_colors;
      setCurrentTheme(colors.classes);
      if (colors.column_values) {
        columnStyleRules = { ...colors.column_values };
      }
    } else {
      // Normal log message.
      const currentData = { ...dataObj, time: formatDate(new Date()) };
      logSource.append_log(currentData);
    }
  };
})();
