import Blockly, { GeneratorMap } from 'blockly';
import { TypedVariableModal } from '@blockly/plugin-typed-variable-modal';

// 演算子の優先順位定義
// 参考: https://en.cppreference.com/w/cpp/language/operator_precedence

/// number, "", etc
export const ORDER_ATOMIC = 0;
/// ::
export const ORDER_SCOPE_RESOLUTION = 1;
/// a++
export const ORDER_INCREMENT = 2;
/// a--
export const ORDER_DECREMENT = 2;
/// f()
export const ORDER_FUNCTION_CALL = 2.1;
/// type()
export const ORDER_FUNCTIONAL_CAST = 2.2;
/// . [] ->
export const ORDER_MEMBER = 2.3;
/// ++a
export const ORDER_PRE_INCREMENT = 3;
/// --a
export const ORDER_PRE_DECREMENT = 3;
/// +
export const ORDER_UNARY_PLUS = 3.1;
/// -
export const ORDER_UNARY_NEGATION = 3.2;
/// ! ~
export const ORDER_LOGICAL_NOT = 3.3;
/// deref *a
export const ORDER_DEREFERENCE = 3.4;
/// &a
export const ORDER_ADDRESS_OF = 3.5;
/// (type)
export const ORDER_CAST = 3.6;
/// sizeof
export const ORDER_SIZEOF = 3.7;
/// co_await
export const ORDER_CO_AWAIT = 3.8;
/// new, new[]
export const ORDER_NEW = 3.9;
/// delte, delete[]
export const ORDER_DELETE = 3.9;
/// pointer to member .* ->*
export const ORDER_POINTER_TO_MEMBER = 4;
/// a * b
export const ORDER_MULTIPLICATION = 5;
/// a / b
export const ORDER_DIVISION = 5.1;
/// a % b
export const ORDER_MODULUS = 5.2;
/// a - b
export const ORDER_SUBTRACTION = 6;
/// a + b
export const ORDER_ADDITION = 6;
/// << >>
export const ORDER_BITWISE_SHIFT = 7;
/// <=>
export const ORDER_THREEWAY_COMPARISON = 8;
/// < <= > >=
export const ORDER_RELATIONAL = 9;
/// == !=
export const ORDER_EQUALITY = 10;
/// &
export const ORDER_BITWISE_AND = 11;
/// ^
export const ORDER_BITWISE_XOR = 12;
/// |
export const ORDER_BITWISE_OR = 13;
/// &&
export const ORDER_LOGICAL_AND = 14;
/// ||
export const ORDER_LOGICAL_OR = 15;
/// a?b:c
export const ORDER_CONDITIONAL = 16;
/// = += -= **= *= /= %= <<= >>= ...
export const ORDER_ASSIGNMENT = 16.1;
/// co_yield
export const ORDER_CO_YIELD = 16.2;
/// throw
export const ORDER_THROW = 16.3;
/// ,
export const ORDER_COMMA = 17;
/// (...)
export const ORDER_NONE = 99;

// カギ括弧の展開ルール
// 1 + (2 + (3 + (4 + (5 + 6))))
// みたいなコードが生成されないようにするためのルール
// JavaScriptの定義からほぼ拝借
export const ORDER_OVERRIDES = [
  // (foo()).bar -> foo().bar
  // (foo())[0] -> foo()[0]
  [ORDER_FUNCTION_CALL, ORDER_MEMBER],
  [ORDER_FUNCTION_CALL, ORDER_POINTER_TO_MEMBER],
  // (foo())() -> foo()()
  [ORDER_FUNCTION_CALL, ORDER_FUNCTION_CALL],
  // (foo.bar).baz -> foo.bar.baz
  // (foo.bar)[0] -> foo.bar[0]
  // (foo[0]).bar -> foo[0].bar
  // (foo[0])[1] -> foo[0][1]
  [ORDER_MEMBER, ORDER_MEMBER],
  [ORDER_MEMBER, ORDER_POINTER_TO_MEMBER],
  [ORDER_POINTER_TO_MEMBER, ORDER_MEMBER],
  [ORDER_POINTER_TO_MEMBER, ORDER_POINTER_TO_MEMBER],
  // (foo.bar)() -> foo.bar()
  // (foo[0])() -> foo[0]()
  [ORDER_MEMBER, ORDER_FUNCTION_CALL],
  [ORDER_POINTER_TO_MEMBER, ORDER_FUNCTION_CALL],

  // !(!foo) -> !!foo
  [ORDER_LOGICAL_NOT, ORDER_LOGICAL_NOT],
  // a * (b * c) -> a * b * c
  [ORDER_MULTIPLICATION, ORDER_MULTIPLICATION],
  // a ± (b ± c) -> a ± b ± c
  [ORDER_ADDITION, ORDER_ADDITION],
  // a && (b && c) -> a && b && c
  [ORDER_LOGICAL_AND, ORDER_LOGICAL_AND],
  // a || (b || c) -> a || b || c
  [ORDER_LOGICAL_OR, ORDER_LOGICAL_OR],
  // a & (b & c) -> a & b & c
  [ORDER_BITWISE_AND, ORDER_BITWISE_AND],
  // a | (b | c) -> a | b | c
  [ORDER_BITWISE_OR, ORDER_BITWISE_OR],
];

// keyword一覧
// 参考:https://en.cppreference.com/w/cpp/keyword
const KEYWORDS =
  'alignas,alignof,and,and_eq,asm,atomic_cancel,atomic_commit,atomic_noexcept,auto,bitand,bitor,bool,break,case,catch,char,char8_t,char16_t,char32_t,class,compl,concept,export const,export consteval,export constexpr,export constinit,export const_cast,continue,co_await,co_return,co_yield,decltype,default,delete,do,double,dynamic_cast,else,enum,explicit,export,extern,false,float,for,friend,goto,if,inline,int,long,mutable,namespace,new,noexcept,not,not_eq,nullptr,operator,or,or_eq,private,protected,public,reflexpr,register,reinterpret_cast,requires,return,short,signed,sizeof,static,static_assert,static_cast,struct,switch,synchronized,template,this,thread_local,throw,true,try,typedef,typeid,typename,union,unsigned,using,virtual,void,volatile,wchar_t,while,xor,xor_eq,final,override,transaction_safe,transaction_safe_dynamic,import,module,std,boost,glue_,uint8_t,int8_t,uint16_t,int16_t,uint32_t,int32_t,uint64_t,int64_t,size_t,';

export interface CType {
  /** GUIで表示する名前 */
  name: string;
  /** C++での型名 */
  ctype: string;
  /** 初期値の生成文字列 */
  defaultValue: string;
}

export class CppGenerator extends Blockly.Generator {
  private types: { [key: string]: CType };
  private variableCode: string;
  private taskNames: { [key: string]: number };

  constructor() {
    super('C++');
    this.ORDER_OVERRIDES = ORDER_OVERRIDES;
    // C++のキーワードと、Blocklyが使うキーワードを登録
    this.addReservedWords(KEYWORDS + Object.getOwnPropertyNames(Blockly.utils.global).join(','));
    this.isInitialized = false;
    this.types = {
      Boolean: {
        name: '真理値',
        ctype: 'bool',
        defaultValue: 'false',
      },
      Integer: {
        name: '整数',
        ctype: 'int',
        defaultValue: '0',
      },
      String: {
        name: '文字列',
        ctype: 'std::string',
        defaultValue: '""',
      },
    };
    this.variableCode = '';
    this.taskNames = {};
  }

  /**
   * ハイライトを有効・無効。デフォルトは無効です。
   */
  setHighlight(enable: boolean) {
    this.STATEMENT_PREFIX = enable? 'mylib__::highlightBlock(%1);\n': '';
  }

  init(workspace: Blockly.Workspace) {
    super.init(workspace);
    this.taskNames = {};

    if (!this.nameDB_) this.nameDB_ = new Blockly.Names(this.RESERVED_WORDS_);
    else this.nameDB_.reset();

    const nameDB = this.nameDB_ as Blockly.Names;
    nameDB.setVariableMap(workspace.getVariableMap());
    nameDB.populateVariables(workspace);
    nameDB.populateProcedures(workspace);
    // ユーザーには見えない変数の登録
    for (const v of Blockly.Variables.allDeveloperVariables(workspace))
      nameDB.getName(v, Blockly.Names.DEVELOPER_VARIABLE_TYPE);

    let code = '';
    // ユーザー定義変数
    for (const v of Blockly.Variables.allUsedVarModels(workspace)) {
      const ctype = this.types[v.type];
      if (!ctype) throw new Error(`Type ${v.type} is not defined.`);

      const name = nameDB.getName(v.getId(), Blockly.VARIABLE_CATEGORY_NAME);
      code += `static ${ctype.ctype} ${name} = ${ctype.defaultValue};\n`;
    }
    this.variableCode = code;

    this.isInitialized = true;
  }

  getTaskName(name: string): string {
    if (!this.taskNames) this.taskNames = {};
    const names = this.taskNames;
    if (!(name in names)) {
      names[name] = 1;
      return name;
    }
    const i = names[name];
    names[name] = i + 1;
    return name + i.toString();
  }

  finish(code: string): string {
    this.isInitialized = false;
    this.nameDB_.reset();
    return (
      '#include "mylib.hpp"\n' +
      'namespace user__ {\n' +
      this.variableCode +
      '\nvoid __createTasks__()\n{\n' +
      code +
      '\n}' + // __createTasks__
      '\n}'
    ); // user__
  }

  scrub_(block: Blockly.Block, code: string, opt_thisOnly?: boolean): string {
    const nextBlock = block.nextConnection && block.nextConnection.targetBlock();
    const nextCode = opt_thisOnly ? '' : this.blockToCode(nextBlock);
    return code + nextCode;
  }

  injectId(msg: string, block: Blockly.Block): string {
    const id = block.id.replace(/\$/g, '$$$$');
    return msg.replace(/%1/g, `"${id}"`);
  }

  blockToCode(block: Blockly.Block, opt_thisOnly?: boolean): string | string[] {
    if (!block) return '';
    if (block.type != 'flag-start') {
      return super.blockToCode(block, opt_thisOnly);
    }
    if (!block.isEnabled() || block.getInheritedDisabled()) return '';

    const createTask = this.INDENT + (this as any)[block.type].call(block, block); // eslint-disable-line
    const body = this.scrub_(block, '', opt_thisOnly);
    const ibody = this.prefixLines(body, this.INDENT + this.INDENT);
    return (
      createTask +
      ibody +
      // タスク終了処理の追加
      this.INDENT +
      this.INDENT +
      '::vTaskDelete(NULL);\n' +
      this.INDENT +
      '});\n'
    );
  }

  getType(blockType: string): CType | undefined {
    return this.types[blockType];
  }
  createVariableCategory(workspace: Blockly.WorkspaceSvg) {
    const CATEGORY = 'TYPED_VARIABLE';
    // TypedVariableModelとregisterToolboxCategoryCallbackを繋ぐ名前
    const CALLBACK_NAME = 'cpp-variable-category-cb';
    // カテゴリを作成
    workspace.registerToolboxCategoryCallback(CATEGORY, workspace => {
      let xmlList: Element[] = [];
      const button = document.createElement('button');
      button.setAttribute('text', '変数を作成する');
      button.setAttribute('callbackKey', CALLBACK_NAME);
      xmlList.push(button);
      const blockList = Blockly.VariablesDynamic.flyoutCategoryBlocks(workspace);
      xmlList = xmlList.concat(blockList);
      return xmlList;
    });

    // 型の処理
    const type: [string, string][] = [];
    for (const blockType in this.types) type.push([this.types[blockType].name, blockType]);

    // 作成
    new TypedVariableModal(workspace, CALLBACK_NAME, type, {
      TYPED_VAR_MODAL_TITLE: '変数を作成する',
      TYPED_VAR_MODAL_INVALID_NAME: '名前を使用できません。他の名前を使って下さい。',
      TYPED_VAR_MODAL_TYPES_LABEL: '変数の型',
      TYPED_VAR_MODAL_VARIABLE_NAME_LABEL: '変数名',
    }).init();
  }
}
export const Cpp = new CppGenerator() as CppGenerator & GeneratorMap;

import './block';
import './variables';
