/*

 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.

 * 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.

 */



const { ErrorType, ErrorLevel, LogType, requireTypescriptModule, checkVersionNeedCheck, createErrorInfo, ErrorValueInfo } = require('./utils');

const { addAPICheckErrorLogs } = require('./compile_info');

const { checkSmallHump } = require('./check_hump');

const ts = requireTypescriptModule();



// check if on and off functions show in pair

function checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames) {

  for (const value of onEventCheckNames) {

    if (!offEventAllNames.has(value)) {

      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);

      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,

        ErrorLevel.MIDDLE);

    }

  }

  for (const value of offEventCheckNames) {

    if (!onEventAllNames.has(value)) {

      const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_ON_AND_OFF_PAIR, []);

      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.API_PAIR_ERRORS, checkErrorResult, LogType.LOG_API,

        ErrorLevel.MIDDLE);

    }

  }

}



function checkTheFirstParameter(node, sourcefile, fileName) {

  // check the version

  if (!checkVersionNeedCheck(node)) {

    return;

  }

  if (node.parameters && node.parameters.length > 0 && node.parameters[0].type) {

    const firstParameterType = node.parameters[0].type;

    // check the type of first parameter

    if ((firstParameterType.kind === ts.SyntaxKind.LiteralType && firstParameterType.literal.kind ===

      ts.SyntaxKind.StringLiteral)) {

      // if the first parameter is string

      const parameterName = firstParameterType.literal.text;

      if (!checkSmallHump(parameterName)) {

        const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_SMALL_HUMP, [parameterName]);

        addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,

          ErrorLevel.MIDDLE);

        return;

      }

    } else if (firstParameterType.kind === ts.SyntaxKind.StringKeyword) {

      // if the first parameter is 'string'

      return;

    } else {

      let checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_STRING, []);

      if (firstParameterType.typeName && firstParameterType.typeName.escapedText === '') {

        checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_NAME_NULL, []);

      }

      addAPICheckErrorLogs(node, sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,

        ErrorLevel.MIDDLE);

    }

  }

}



function isBasicType(node) {

  if (node.type !== undefined) {

    const nodeKind = node.type.kind;

    const basicTypes = new Set([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword, ts.SyntaxKind.BooleanKeyword, ts.SyntaxKind.UndefinedKeyword,

      ts.SyntaxKind.LiteralType]);

    if (basicTypes.has(nodeKind)) {

      return true;

    }

  }

  return false;

}



// check if the callback parameter of off function is optional

function checkOffFunctions(nodes, sourcefile, fileName) {

  let isAllCallbackMandatory = true; 

  let someoneMissingCallback = false;

  let someoneHasCallback = false;

  for (let node of nodes) {

    if (node.parameters.length === 0) {

      continue;

    }

    const lastParameter = node.parameters[node.parameters.length - 1];

    if (isBasicType(lastParameter)) {

      someoneMissingCallback = true;

    } else {

      someoneHasCallback = true;

      if (lastParameter.questionToken !== undefined) {

        isAllCallbackMandatory = false;

      }

    }

  }

  // has off fucntion with callback parameter which is not optional, and doesn't have off function without callback parameter

  if (isAllCallbackMandatory && !someoneMissingCallback) {

    const checkErrorResult = createErrorInfo(ErrorValueInfo.ERROR_EVENT_CALLBACK_OPTIONAL, []);

    addAPICheckErrorLogs(nodes[0], sourcefile, fileName, ErrorType.PARAMETER_ERRORS, checkErrorResult, LogType.LOG_API,

      ErrorLevel.MIDDLE);

  }

}



function extendEventNames(node, allNames, checkNames) {

  const nodeType = node.parameters[0].type;

  let eventName = '';

  if (nodeType.kind === ts.SyntaxKind.LiteralType) {

    eventName = nodeType.literal.text;

    allNames.add(eventName);

  } else if (nodeType.kind === ts.SyntaxKind.StringKeyword) {

    eventName = 'string';

    allNames.add(eventName);

  }

  if (checkVersionNeedCheck(node) && eventName !== '') {

    checkNames.add(eventName);

  }

  return eventName;

}



function extendEventNodes(node, eventName, nodesSet) {

// store the off function node based on their names

  if (!nodesSet.get(eventName)) {

    nodesSet.set(eventName, [node]);

  } else {

    let curNodes = nodesSet.get(eventName);

    curNodes.push(node);

    nodesSet.set(eventName, curNodes);

  }

}



// handle event subscription node

function handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames, onEventCheckNames,

  offEventAllNames, offEventCheckNames, offEventNodes) {

  if (childNode.parameters && childNode.parameters.length > 0 && childNode.parameters[0].type) {

    // judge the event subscription api type

    if (childNodeName === 'on') {

      extendEventNames(childNode, onEventAllNames, onEventCheckNames);

      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);

    } else if (childNodeName === 'off') {

      let eventName = extendEventNames(childNode, offEventAllNames, offEventCheckNames);

      extendEventNodes(childNode, eventName, offEventNodes);

      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);

    } else if (childNodeName === 'once' || childNodeName === 'emit') {

      checkTheFirstParameter(childNode, sourcefile, fileName, childNodeName);

    }

  }

}



function checkEventSubscription(node, sourcefile, fileName) {

  // if the node is namespace or interface

  if ((ts.isInterfaceDeclaration(node)) || ts.isModuleBlock(node) || ts.isModuleDeclaration(node) ||

    ts.isClassDeclaration(node) || node === sourcefile) {

    const onEventAllNames = new Set();

    const onEventCheckNames = new Set();

    const offEventAllNames = new Set();

    const offEventCheckNames = new Set();

    const offEventNodes = new Map();

    let childNodes = node.members;

    if (ts.isModuleDeclaration(node)) {

      childNodes = node.body.statements;

    }

    if (childNodes === undefined) {

      return;

    }

    childNodes.forEach((childNode) => {

      // if the node is method or function

      if (ts.isFunctionDeclaration(childNode) || ts.isMethodDeclaration(childNode) || ts.isMethodSignature(childNode)) {

        // if the version needed to be check

        let childNodeName = (childNode.name && ts.isIdentifier(childNode.name)) ?

          childNode.name.getText() :

          '';

        handleVariousEventSubscriptionAPI(childNode, childNodeName, sourcefile, fileName, onEventAllNames,

          onEventCheckNames, offEventAllNames, offEventCheckNames, offEventNodes);

      }

    });

    // check the callback parameter of off function is optional

    for (const event of offEventCheckNames) {

      checkOffFunctions(offEventNodes.get(event), sourcefile, fileName);

    }

    // check if the on and off functions of one event shows in pair

    checkOnAndOffAppearInPair(node, sourcefile, fileName, onEventAllNames, onEventCheckNames, offEventAllNames, offEventCheckNames);

  }

}

exports.checkEventSubscription = checkEventSubscription;