/*
 * Copyright (c) 2021 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.
 */

import ts from 'typescript';

import {
  COMPONENT_CONSTRUCTOR_ID,
  COMPONENT_CONSTRUCTOR_PARENT,
  COMPONENT_CONSTRUCTOR_PARAMS,
  COMPONENT_CONSTRUCTOR_UPDATE_PARAMS,
  COMPONENT_CONSTRUCTOR_INITIAL_PARAMS,
  COMPONENT_WATCH_FUNCTION,
  BASE_COMPONENT_NAME,
  INTERFACE_NAME_SUFFIX,
  COMPONENT_CONSTRUCTOR_LOCALSTORAGE,
  BASE_COMPONENT_NAME_PU,
  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU,
  COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU,
  ELMTID,
  COMPONENT_PARAMS_LAMBDA_FUNCTION,
  COMPONENT_IF_UNDEFINED
} from './pre_define';

import { partialUpdateConfig } from '../main';

export function getInitConstructor(members: ts.NodeArray<ts.Node>, parentComponentName: ts.Identifier
): ts.ConstructorDeclaration {
  let ctorNode: any = members.find(item => {
    return ts.isConstructorDeclaration(item);
  });
  if (ctorNode) {
    ctorNode = updateConstructor(ctorNode, [], [], true);
  }
  return initConstructorParams(ctorNode, parentComponentName);
}

export function updateConstructor(ctorNode: ts.ConstructorDeclaration, para: ts.ParameterDeclaration[],
  addStatements: ts.Statement[], isSuper: boolean = false, isAdd: boolean = false,
  parentComponentName?: ts.Identifier): ts.ConstructorDeclaration {
  let modifyPara: ts.ParameterDeclaration[];
  if (para && para.length) {
    modifyPara = Array.from(ctorNode.parameters);
    if (modifyPara) {
      modifyPara.push(...para);
    }
  }
  let modifyBody: ts.Statement[];
  if (addStatements && addStatements.length && ctorNode) {
    modifyBody = Array.from(ctorNode.body.statements);
    if (modifyBody) {
      if (isSuper) {
        modifyBody.unshift(...addStatements);
      } else {
        modifyBody.push(...addStatements);
      }
    }
  }
  if (ctorNode) {
    let ctorPara: ts.ParameterDeclaration[] | ts.NodeArray<ts.ParameterDeclaration> =
      modifyPara || ctorNode.parameters;
    if (isAdd) {
      ctorPara = addParamsType(ctorNode, modifyPara, parentComponentName);
    }
    ctorNode = ts.factory.updateConstructorDeclaration(ctorNode, ctorNode.decorators,
      ctorNode.modifiers, modifyPara || ctorNode.parameters,
      ts.factory.createBlock(modifyBody || ctorNode.body.statements, true));
  }
  return ctorNode;
}

function initConstructorParams(node: ts.ConstructorDeclaration, parentComponentName: ts.Identifier):
  ts.ConstructorDeclaration {
  if (!ts.isIdentifier(parentComponentName)) {
    return;
  }
  const paramNames: Set<string> = !partialUpdateConfig.partialUpdateMode ? new Set([
    COMPONENT_CONSTRUCTOR_ID,
    COMPONENT_CONSTRUCTOR_PARENT,
    COMPONENT_CONSTRUCTOR_PARAMS,
    COMPONENT_CONSTRUCTOR_LOCALSTORAGE
  ]) : new Set([
    COMPONENT_CONSTRUCTOR_PARENT,
    COMPONENT_CONSTRUCTOR_PARAMS,
    COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU,
    ELMTID,
    COMPONENT_PARAMS_LAMBDA_FUNCTION
  ]);
  const newParameters: ts.ParameterDeclaration[] = Array.from(node.parameters);
  if (newParameters.length !== 0) {
    // @ts-ignore
    newParameters.splice(0, newParameters.length);
  }
  paramNames.forEach((paramName: string) => {
    newParameters.push(ts.factory.createParameterDeclaration(undefined, undefined, undefined,
      ts.factory.createIdentifier(paramName), undefined, undefined,
      paramName === ELMTID || paramName === COMPONENT_PARAMS_LAMBDA_FUNCTION ? paramName === ELMTID ?
        ts.factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusToken, ts.factory.createNumericLiteral('1')) :
        ts.factory.createIdentifier(COMPONENT_IF_UNDEFINED) : undefined));
  });

  return ts.factory.updateConstructorDeclaration(node, undefined, node.modifiers, newParameters,
    node.body);
}

function addParamsType(ctorNode: ts.ConstructorDeclaration, modifyPara: ts.ParameterDeclaration[],
  parentComponentName: ts.Identifier): ts.ParameterDeclaration[] {
  const tsPara: ts.ParameterDeclaration[] | ts.NodeArray<ts.ParameterDeclaration> =
    modifyPara || ctorNode.parameters;
  const newTSPara: ts.ParameterDeclaration[] = [];
  tsPara.forEach((item) => {
    let parameter: ts.ParameterDeclaration = item;
    switch (item.name.escapedText) {
      case COMPONENT_CONSTRUCTOR_ID:
        parameter = ts.factory.updateParameterDeclaration(item, item.decorators, item.modifiers,
          item.dotDotDotToken, item.name, item.questionToken,
          ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), item.initializer);
        break;
      case COMPONENT_CONSTRUCTOR_PARENT:
        parameter = ts.factory.createParameterDeclaration(item.decorators, item.modifiers,
          item.dotDotDotToken, item.name, item.questionToken,
          ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(
            !partialUpdateConfig.partialUpdateMode ? BASE_COMPONENT_NAME : BASE_COMPONENT_NAME_PU), undefined),
          item.initializer);
        break;
      case COMPONENT_CONSTRUCTOR_PARAMS:
        parameter = ts.factory.updateParameterDeclaration(item, item.decorators, item.modifiers,
          item.dotDotDotToken, item.name, item.questionToken,
          ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(
            parentComponentName.getText() + INTERFACE_NAME_SUFFIX), undefined), item.initializer);
        break;
      case COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU:
        parameter = ts.factory.createParameterDeclaration(item.decorators, item.modifiers, item.dotDotDotToken,
          item.name, ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(
            ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_TYPE_PU), undefined), item.initializer);
        break;
    }
    newTSPara.push(parameter);
  });
  return newTSPara;
}

export function addConstructor(ctorNode: any, watchMap: Map<string, ts.Node>,
  parentComponentName: ts.Identifier): ts.ConstructorDeclaration {
  const watchStatements: ts.ExpressionStatement[] = [];
  watchMap.forEach((value, key) => {
    const watchNode: ts.ExpressionStatement = ts.factory.createExpressionStatement(
      ts.factory.createCallExpression(
        ts.factory.createPropertyAccessExpression(
          ts.factory.createThis(),
          ts.factory.createIdentifier(COMPONENT_WATCH_FUNCTION)
        ),
        undefined,
        [
          ts.factory.createStringLiteral(key),
          ts.isStringLiteral(value) ?
            ts.factory.createPropertyAccessExpression(ts.factory.createThis(),
              ts.factory.createIdentifier(value.text)) : value as ts.PropertyAccessExpression
        ]
      ));
    watchStatements.push(watchNode);
  });
  const callSuperStatement: ts.Statement = createCallSuperStatement();
  const updateWithValueParamsStatement: ts.Statement = createUPdWithValStatement();
  return updateConstructor(updateConstructor(ctorNode, [], [callSuperStatement], true), [],
    [updateWithValueParamsStatement, ...watchStatements], false, true, parentComponentName);
}

function createCallSuperStatement(): ts.Statement {
  if (!partialUpdateConfig.partialUpdateMode) {
    return ts.factory.createExpressionStatement(ts.factory.createCallExpression(
      ts.factory.createSuper(), undefined,
      [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_ID),
        ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT),
        ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE)]));
  } else {
    return ts.factory.createExpressionStatement(
      ts.factory.createCallExpression(ts.factory.createSuper(), undefined,
        [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARENT),
          ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_LOCALSTORAGE_PU),
          ts.factory.createIdentifier(ELMTID)]));
  }
}

function createUPdWithValStatement(): ts.Statement {
  return ts.factory.createExpressionStatement(
    ts.factory.createCallExpression(
      ts.factory.createPropertyAccessExpression(
        ts.factory.createThis(),
        ts.factory.createIdentifier(
          !partialUpdateConfig.partialUpdateMode ?
            COMPONENT_CONSTRUCTOR_UPDATE_PARAMS : COMPONENT_CONSTRUCTOR_INITIAL_PARAMS
        )
      ),
      undefined,
      [ts.factory.createIdentifier(COMPONENT_CONSTRUCTOR_PARAMS)]
    )
  );
}