import { log } from 'console';

export const BLANK_LOGGER_CONFIG: LogConfig = {
  logToConsole: false,
  logToFile: false,
  component: {
  },
  origins: {
    videoRescan: { disabled: true},
  }
}

export interface LogSourceOptions {
  src?: string;
  origin?: string;
}

export interface LogComponentConfig {
  enabled: boolean;
}

export interface LogConfig {
  logToConsole?: boolean;
  logToFile?: boolean;
  stopAfter?: number;
  component?: {[x: string]: LogComponentConfig};
  origins?: {
    videoRescan?: {
      disabled: boolean;
    }
  }
}

const STORAGE_LOG_SETTINGS_KEY = 'uw-log-config';

export class LogAggregator {
  private segment: string;
  private config: LogConfig;

  private startTime: number;

  history: any[];

  static async getConfig() {
    let ret: any = await chrome.storage.local.get(STORAGE_LOG_SETTINGS_KEY);

    if (process.env.CHANNEL === 'dev') {
      try {
        console.info("[LogAggregator::getSaved] Got settings:", JSON.parse(ret[STORAGE_LOG_SETTINGS_KEY]));
      } catch (e) {
        console.info("[LogAggregator::getSaved] No settings.", ret)
      }
    }

    try {
      return JSON.parse(ret[STORAGE_LOG_SETTINGS_KEY]);
    } catch (e) {
      return JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
    }
  }

  static async saveConfig(conf: LogConfig) {
    try {
      const confCp = JSON.parse(JSON.stringify(conf));
      if (process.env.CHANNEL === 'dev') {
        console.info('Saving logger conf:', confCp)
      }

      await chrome.storage.local.set({[STORAGE_LOG_SETTINGS_KEY]: JSON.stringify(confCp)} );
    } catch (e) {
      console.warn('[LogAggregator::saveConfig] Error while trying to save logger config:', e);
    }
  }

  static syncConfig(callback: (x) => void) {
    chrome.storage.onChanged.addListener( (changes: any, area) => {
      if (changes.uwLogger) {
        const newLoggerConf = JSON.parse(changes.uwLogger.newValue)
        if (process.env.CHANNEL === 'dev') {
          console.info('Logger settings reloaded. New conf:', newLoggerConf);
        }
        callback(newLoggerConf);
      }
    });
  }

  constructor(segment: string) {
    this.segment = segment;

    chrome.storage.onChanged.addListener((changes, area) => {
      this.storageChangeListener(changes, area)
    });

    this.init();
  }

  canLog(component: string, sourceOptions?: LogSourceOptions): boolean {
    // component is not in config, so we add a blank entry
    if (this.config && !this.config.component[component]) {
      this.config.component[component] = {enabled: false};
      LogAggregator.saveConfig(this.config);
      return false;
    }
    if (this.config?.component?.[component]?.enabled) {
      if (sourceOptions?.origin && this.config?.origins?.[sourceOptions.origin]?.disabled) {
        return false;
      }
      return true;
    }

    return false;
  }

  private storageChangeListener(changes, area) {
    if (!changes[STORAGE_LOG_SETTINGS_KEY]) {
      return;
    }

    try {
      this.config = JSON.parse(changes[STORAGE_LOG_SETTINGS_KEY].newValue);
    } catch (e) {
      console.warn('[uwLogger] Error while trying to parse new conf for logger:', e, '\nWe received the following changes:', changes, 'for area:', area);
    }

    // This code can only execute if user tried to enable or disable logging
    // through the popup. In cases like this, we do not gate the console.log
    // behind a check, since we _always_ want to have this feedback in response
    // to an action.
    console.info(
      '[uwLogger] logger config changed! New configuration:',
      this.config, '\nraw changes:', changes, 'area?', area,
      '\n————————————————————————————————————————————————————————————————————————\n\n\n\n\n\n\n\n\n\n\n\n-----\nLogging with new settings starts now.'
    );

    this.init(this.config);
  }

  private parseStack() {
    const trace = (new Error()).stack;

    const stackInfo: any = {};
    // we turn our stack into array and remove the "file::line" part of the trace,
    // since that is useless because minification/webpack
    stackInfo['stack'] = {trace: trace.split('\n').map(a => a.split('@')[0])};

    // here's possible sources that led to this log entry
    stackInfo['periodicPlayerCheck'] = false;
    stackInfo['periodicVideoStyleChangeCheck'] = false;
    stackInfo['aard'] = false;
    stackInfo['keyboard'] = false;
    stackInfo['popup'] = false;
    stackInfo['mousemove'] = false;
    stackInfo['exitLogs'] = false;

    // here we check which source triggered the action. There can be more
    // than one source, too, so we don't break when we find the first one
    for (const line of stackInfo.stack.trace) {
      if (line === 'doPeriodicPlayerElementChangeCheck') {
        stackInfo['periodicPlayerCheck'] = true;
      } else if (line === 'doPeriodicFallbackChangeDetectionCheck') {
        stackInfo['periodicVideoStyleChangeCheck'] = true;
      } else if (line === 'frameCheck') {
        stackInfo['aard'] = true;
      } else if (line === 'execAction') {
        stackInfo['keyboard'] = true;
      } else if (line === 'processReceivedMessage') {
        stackInfo['popup'] = true;
      } else if (line === 'handleMouseMove') {
        stackInfo['mousemove'] = true;
      }
    }

    // exitLog overrides any other exclusions, so we look for it separately.
    // we also remove some of the unnecessary messages to reduce log file size
    for(let i = 0; i < stackInfo.stack.trace.length; i++) {
      if (stackInfo.stack.trace[i] === 'finish') {
        stackInfo['exitLogs'] = true;
        break;
      }

      // if we hit one of these, we remove the rest of the array and call it a
      // day. Chances are there's nothing of value past this point.
      if (stackInfo.stack.trace[i].indexOf('promise callback') !== -1
          || stackInfo.stack.trace[i].indexOf('asyncGeneratorStep') !== -1
          || stackInfo.stack.trace[i].indexOf('_asyncToGenerator') !== -1
          || stackInfo.stack.trace[i].startsWith('_next')) {
        stackInfo.stack.trace.splice(i);
        break;
      }
    }

    return stackInfo;
  }

  // TODO: implement this
  private stopLogging() {

  }

  async init(config?: LogConfig) {
    if (!config) {
      config = await LogAggregator.getConfig();
    }

    this.config = config;
    this.startTime = performance.now();

    if (this.config?.stopAfter) {
      setTimeout(
        () => {
          this.stopLogging();
        },
        this.config.stopAfter * 1000
      );
    }
  }

  /**
   * Logs message to console.
   * This function does NOT check before logging, as canLog check should now be performed
   * BEFORE ComponentLogger starts processing log calls.
   * @param logLevel
   * @param message
   * @param data
   */
  log(logLevel: string, message: string, ...data: any[]) {
    if (this.config.logToFile) {
      console.warn('log to file not implemented!')
    }

    if (this.config.logToConsole){
      console[logLevel](`[${this.segment}]>>${message}`, ...data, {stack: this.parseStack()});
    }
  }
}
