/* eslint-disable prefer-const */
/* eslint-disable dot-notation */
/* eslint-disable no-unused-vars */
/* eslint-disable eqeqeq */
/* eslint-disable camelcase */
/* eslint-disable no-lone-blocks */
// We require the Hardhat Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
//
// When running the script with `npx hardhat run <script>` you'll find the Hardhat
// Runtime Environment's members available in the global scope.

const { ContractFactory } = require("ethers");
const hre = require("hardhat");
// const { upgrades } = require("@openzeppelin/hardhat-upgrades");

const HORSE_GENE = {
  DARLEY_ARABIAN: ["VVEEAADDTTHH"],
  GODOLPHIN_ARABIAN: [
    "VVEEAADDTThh",
    "VVEEAADDttHH",
    "VVEEAAddTTHH",
    "VVEEaaDDTTHH",
    "VVeeAADDTTHH",
    "vvEEAADDTTHH",
  ],
  BYERLY_TURK: [
    "VVEEAADDtthh",
    "VVEEAAddTThh",
    "VVEEaaDDTThh",
    "VVeeAADDTThh",
    "vvEEAADDTThh",
    "VVEEAAddttHH",
    "VVEEaaDDttHH",
    "VVeeAADDttHH",
    "vvEEAADDttHH",
    "VVEEaaddTTHH",
    "VVeeAAddTTHH",
    "vvEEAAddTTHH",
    "VVeeaaDDTTHH",
    "vvEEaaDDTTHH",
    "vveeAADDTTHH",
  ],
  SATOSHI_NAKAMOTO: [
    "VVEEAAddtthh",
    "VVEEaaDDtthh",
    "VVEEaaddTThh",
    "VVEEaaddttHH",
    "VVeeAADDtthh",
    "VVeeAAddTThh",
    "VVeeAAddttHH",
    "VVeeaaDDTThh",
    "VVeeaaDDttHH",
    "VVeeaaddTTHH",
    "vvEEAADDtthh",
    "vvEEAAddTThh",
    "vvEEAAddttHH",
    "vvEEaaddTTHH",
    "vvEEaaDDTThh",
    "vvEEaaDDttHH",
    "vveeAADDTThh",
    "vveeAADDttHH",
    "vveeAAddTTHH",
    "vveeaaDDTTHH",
  ],
};

const ADDR_MINT_RECEIVER = process.env.ADDR_MINT_RECEIVER;
const ADDR_BONUS_POOL = process.env.ADDR_BONUS_POOL;
const ADDR_BONUS_BURNED = process.env.ADDR_BONUS_BURNED;
const ADDR_BONUS_PLATFORM = process.env.ADDR_BONUS_PLATFORM;
const ADDR_FEE_RECEIVER = process.env.ADDR_FEE_RECEIVER;
const ADDR_USDT_TOKEN = process.env.ADDR_USDT_TOKEN;
const ADDR_API_SERVER = process.env.ADDR_API_SERVER;

// 装备类型
// 1 马头饰
// 2 马护甲
// 3 马尾饰
// 4 马蹄饰(暂未使用)

// 装备款式
// 1 - 5
// 1、 圣诞 套装
// 2、 庄重 套装
// 3、 淡雅 套装
// 4、 华丽 套装
// 5、 喜庆 套装
const BATCH_HORSE_COUNT = 10;
const BATCH_EQUIP_COUNT = 50;
const EQUIP_TYPE_COUNT = 3;
const EQUIP_STYLE_COUNT = 5;

const GAME_DISTANCE = {
  1200: { fee: 5, award: 20 },
  2400: { fee: 10, award: 20 },
  3600: { fee: 15, award: 20 },
};

const HORSE_GENE_PROBABILITIES = [
  [3000, 2000, 4000, 1000],
  [4000, 2600, 2800, 600],
  [4800, 3000, 1900, 300],
  [5500, 3300, 1100, 100],
];
const MYSTERY_BOX_NAMES = ["PLATINUM", "GOLD", "SILVER", "BRONZE"];
const MYSTERY_BOX_PRICES = [400, 300, 200, 100];
const MYSTERY_BOX_HORSE_TYPES = [
  "SATOSHI_NAKAMOTO",
  "BYERLY_TURK",
  "GODOLPHIN_ARABIAN",
  "DARLEY_ARABIAN",
];
const MYSTERY_BOX_HORSE_COUNT = [100, 100, 100, 100];

const RELY_ON_ADMIN_MAP = [
  { contract: "EquipToken", params: ["EquipExtra"] },
  { contract: "HorseToken", params: ["RaceExtra"] },
  { contract: "ArenaToken", params: ["ArenaExtra"] },
];

const RELY_ON_MINTER_MAP = [
  { contract: "Coin", params: ["EquipExtra"] },
  { contract: "Coin", params: ["RaceExtra"] },
  { contract: "Coin", params: ["RaceExtra1"] },
  { contract: "Coin", params: ["RaceExtra2"] },
  { contract: "Coin", params: ["ArenaExtra"] },
  { contract: "Coin", params: ["ArenaExtra1"] },
  { contract: "Coin", params: ["ArenaExtra2"] },
  { contract: "Coin", params: ["ArenaExtra3"] },
  { contract: "Coin721", params: ["EquipExtra"] },
  { contract: "Coin721", params: ["RaceExtra2"] },
];

const RELY_ON_PROGRAM_MAP = [
  { contract: "EquipAttr", params: ["EquipExtra"] },
  { contract: "EquipAttr", params: ["RaceExtra1"] },
  { contract: "RaceAttr1", params: ["ArenaExtra1"] },
  { contract: "RaceAttr1", params: ["RaceExtra2"] },
  { contract: "RaceAttr1", params: ["RaceExtra1"] },
  { contract: "RaceAttr1", params: ["RaceExtra"] },
  { contract: "RaceAttr1_1", params: ["EquipExtra"] },
  { contract: "RaceAttr1_1", params: ["RaceExtra"] },
  { contract: "RaceAttr1_1", params: ["RaceExtra1"] },
  { contract: "RaceAttr1_1", params: ["RaceExtra2"] },
  { contract: "RaceAttr1_1", params: ["ArenaExtra"] },
  { contract: "RaceAttr1_1", params: ["ArenaExtra1"] },
  { contract: "RaceAttr1_1", params: ["ArenaExtra2"] },
  { contract: "RaceAttr1_1", params: ["ArenaExtra3"] },
  { contract: "ArenaAttr", params: ["ArenaExtra"] },
  { contract: "ArenaAttr", params: ["ArenaExtra1"] },
  { contract: "ArenaAttr", params: ["ArenaExtra2"] },
  { contract: "ArenaAttr", params: ["ArenaExtra3"] },
  { contract: "RaceNFTAttr", params: ["EquipAttr"] },
  { contract: "RaceNFTAttr", params: ["RaceAttr1"] },
  { contract: "RaceNFTAttr", params: ["RaceAttr1_1"] },
  { contract: "RaceNFTAttr", params: ["ArenaAttr"] },
  { contract: "RaceBonusDistribute", params: ["ArenaExtra1"] },
  { contract: "RaceBonusPool", params: ["RaceBonusDistribute"] },
  { contract: "RaceBonusData", params: ["RaceBonusPool"] },
  { contract: "RaceToken", params: ["ArenaExtra1"] },
  { contract: "HorseCourseAttr", params: ["ArenaExtra3"] },
  { contract: "HorseCourseAttrOpera", params: ["ArenaExtra3"] },
  { contract: "HorseCourseAttr", params: ["HorseCourseAttrOpera"] },
];

const OFFICIAL_CONTRACT_MAP = [
  { contract: "Constant", params: ["Coin721"] },
  { contract: "Constant", params: ["EquipExtra"] },
  { contract: "Constant", params: ["RaceExtra"] },
  { contract: "Constant", params: ["RaceExtra1"] },
  { contract: "Constant", params: ["RaceExtra2"] },
  { contract: "Constant", params: ["ArenaExtra"] },
  { contract: "Constant", params: ["ArenaExtra1"] },
  { contract: "Constant", params: ["ArenaExtra2"] },
  { contract: "Constant", params: ["ArenaExtra3"] },
];

const TRANSFER_HANDLER_MAP = [
  { contract: "ArenaToken", params: ["ArenaExtra"] },
  { contract: "EquipToken", params: ["EquipExtra"] },
  { contract: "HorseToken", params: ["RaceExtra1"] },
];

const accounts = [{ address: ADDR_API_SERVER }];

const API_SERVER_PROGRAM_MAP = [
  { contract: "ArenaExtra", params: [{ address: ADDR_API_SERVER }] },
  { contract: "ArenaExtra1", params: [{ address: ADDR_API_SERVER }] },
  { contract: "ArenaExtra2", params: [{ address: ADDR_API_SERVER }] },
  { contract: "ArenaExtra3", params: [{ address: ADDR_API_SERVER }] },
  { contract: "HorseCourseAttr", params: [{ address: ADDR_API_SERVER }] },
  { contract: "HorseCourseAttrOpera", params: [{ address: ADDR_API_SERVER }] },
  { contract: "RaceExtra1", params: [{ address: ADDR_API_SERVER }] },
];

const API_SERVER_ADMIN_MAP = [
  { contract: "RaceExtra1", params: [{ address: ADDR_API_SERVER }] },
  { contract: "Constant", params: [{ address: ADDR_API_SERVER }] },
];

let ContractMap = new Map();

async function deployUpgradeContract(
    contractPath,
    contractName,
    addAdmin,
    addrProgram,
    contractOptions
) {
  const factory = await hre.ethers.getContractFactory(
      contractPath,
      contractOptions
  );
  let args = [];
  for (let i = 5; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  const contract = await upgrades.deployProxy(factory, [], {
    initializer: "initialize", // Specify the initializer function
    unsafeAllowLinkedLibraries: true,
  });
  await contract.waitForDeployment();
  var contractAddr = await contract.getAddress();
  contract.address = contractAddr;
  console.log(
      "DeployedContractProxy:",
      contractAddr,
      "for",
      contractName,
      contractPath + "(" + args.join(",") + ")"
  );

  ContractMap.set(contractName, contract);
  if (addAdmin) {
    // await _addAdminSender(contract);
  }
  if (addrProgram != undefined) {
    await contract.addProgram(addrProgram);
  }
  return contract;
}

async function deployContract(
  contractPath,
  contractName,
  addAdmin,
  addrProgram,
  contractOptions
) {
  const factory = await hre.ethers.getContractFactory(
    contractPath,
    contractOptions
  );
  let args = [];
  for (let i = 5; i < arguments.length; i++) {
    args.push(arguments[i]);
  }
  const contract = await factory.deploy.apply(factory, args);
  await contract.waitForDeployment();
  var contractAddr = await contract.getAddress();
  contract.address = contractAddr;
  console.log(
    "DeployedContract:",
    contractAddr,
    "for",
    contractName,
    contractPath + "(" + args.join(",") + ")"
  );
  ContractMap.set(contractName, contract);
  if (addAdmin) {
    // await _addAdminSender(contract);
  }
  if (addrProgram != undefined) {
    await contract.addProgram(addrProgram);
  }
  return contract;
}

async function getContract(contractPath, address) {
  return await hre.ethers.getContractAt(contractPath, address);
}

async function settingBonusDistribute(distribute, nftHorse, bonusPool) {
  const percentForArenaOwner = 800; // 8%
  const percentForWinners = [
    4000, 2000, 960, 640, 400, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  ];

  const set = await distribute.setSettings(
    percentForArenaOwner,
    percentForWinners,
    [ADDR_BONUS_POOL, ADDR_BONUS_BURNED, ADDR_BONUS_PLATFORM],
    [500, 200, 500],
    [bonusPool],
    nftHorse
  );
  await set.wait();
  console.log("SettingBonusDistribute finished.");
  return distribute;
}

async function settingBonusPool(contract, bonusData, metaToken, horseNft) {
  const set = await contract.setContracts(
    bonusData,
    metaToken,
    horseNft,
    ADDR_BONUS_POOL
  );
  await set.wait();
  console.log("SettingBonusPool finished.");
}

async function deployBonus(metaToken, horseNft) {
  const distribute = await deployUpgradeContract(
    "RaceBonusDistribute",
    "RaceBonusDistribute",
    true
  );
  const raceBonusPool = await deployUpgradeContract(
    "RaceBonusPool",
    "RaceBonusPool",
    true
  );
  const raceBonusData = await deployUpgradeContract(
    "RaceBonusData",
    "RaceBonusData",
    true
  );

  await settingBonusPool(
    raceBonusPool,
    getContractAddr(raceBonusData),
    getContractAddr(metaToken),
    getContractAddr(horseNft)
  );
  await settingBonusDistribute(
    distribute,
    getContractAddr(horseNft),
    getContractAddr(raceBonusPool)
  );
  console.log(
    "DeployBonus: setting finished",
    getContractAddr(distribute),
    getContractAddr(raceBonusData),
    getContractAddr(raceBonusPool)
  );
  return {
    distribute: distribute,
    raceBonusData: raceBonusData,
    raceBonusPool: raceBonusPool,
  };
}

async function setMysteryBoxData(NFTMysteryBoxOffering, horseNFT, priceToken) {
  await NFTMysteryBoxOffering.setBox(
    MYSTERY_BOX_NAMES,
    [horseNFT, horseNFT, horseNFT, horseNFT],
    [1000, 1000, 1000, 1000]
  );
  console.log("SetMysteryBoxData: setBox finished.");

  for (let i = 0; i < 4; i++) {
    const prices = [MYSTERY_BOX_PRICES[i]];
    const tokens = [priceToken];
    const probabilities = [0, 0, 0, 0];
    for (let j = 0; j < 4; j++) {
      probabilities[j] = HORSE_GENE_PROBABILITIES[i][j];
    }
    await NFTMysteryBoxOffering.setPrice(MYSTERY_BOX_NAMES[i], tokens, prices);
    console.log(
      "SetMysteryBoxData: setPrice " + MYSTERY_BOX_NAMES[i] + " finished."
    );
    await NFTMysteryBoxOffering.setProbability(
      MYSTERY_BOX_NAMES[i],
      MYSTERY_BOX_HORSE_TYPES,
      probabilities
    );
    console.log(
      "SetMysteryBoxData: setProbability " + MYSTERY_BOX_NAMES[i] + " finished."
    );
  }
}

async function addMysteryBoxToken(address) {
  const NFTMysteryBoxOffering = await getContract(
    "NFTMysteryBoxOffering",
    address
  );
  console.log("addMysteryBoxToken: " + address);
  let k = 1;
  for (let i = 0; i < MYSTERY_BOX_HORSE_COUNT.length; i++) {
    for (let j = 0; j < MYSTERY_BOX_HORSE_COUNT.length; j++) {
      const count =
        (HORSE_GENE_PROBABILITIES[j][i] * MYSTERY_BOX_HORSE_COUNT[j]) / 10000;
      const start = k;
      const end = k + count - 1;
      console.log(
        "addMysteryBoxToken: addToken start " +
          MYSTERY_BOX_NAMES[j] +
          " " +
          start +
          "," +
          end
      );
      await NFTMysteryBoxOffering["addToken(string,uint256,uint256)"](
        MYSTERY_BOX_NAMES[j],
        start,
        end
      );
      console.log(
        "addMysteryBoxToken: addToken ended " +
          MYSTERY_BOX_NAMES[j] +
          " " +
          start +
          "," +
          end
      );
      k += count;
    }
  }
}

async function deployMysteryBox(rewardToken, horseNFT, priceToken) {
  const NFTMysteryBoxOffering = await deployUpgradeContract(
    "NFTMysteryBoxOffering",
    "NFTMysteryBoxOffering"
  );
  const Random = await deployUpgradeContract(
    "Random",
    "Random",
    false,
    getContractAddr(NFTMysteryBoxOffering)
  );
  const Whitelist = await deployUpgradeContract(
    "Whitelist",
    "Whitelist",
    false,
    getContractAddr(NFTMysteryBoxOffering)
  );
  const MysteryBox = await deployUpgradeContract(
    "MysteryBox",
    "MysteryBox",
    false,
    getContractAddr(NFTMysteryBoxOffering)
  );
  const MysteryData = await deployUpgradeContract(
    "MysteryData",
    "MysteryData",
    false,
    getContractAddr(NFTMysteryBoxOffering)
  );
  await NFTMysteryBoxOffering.setContracts(
      getContractAddr(Random),
      getContractAddr(MysteryBox),
      getContractAddr(MysteryData),
      getContractAddr(Whitelist),
      rewardToken
  );
  console.log("DeployMysteryBox: setContracts finished.");
  await NFTMysteryBoxOffering.setAccounts(
    ADDR_MINT_RECEIVER,
    ADDR_MINT_RECEIVER
  );
  console.log("DeployMysteryBox: setAccounts finished.");
  await setMysteryBoxData(NFTMysteryBoxOffering, horseNFT, priceToken);
  console.log("DeployMysteryBox: setMysteryBoxData finished.");
  console.log("DeployMysteryBox: finished.");
}

function getContractAddr(contract) {
  // console.log("contract addr is ", contract.address);
  return contract.address;
}

async function initialDeploy() {
  await deployUpgradeContract("UserLoginContract", "UserLogin", true);

  const constant = await deployConstant();
  const metaToken = await deployContract("MetaToken", "MetaToken", true);
  const raceToken = await deployContract("RaceToken", "RaceToken", true);
  let AddrUsdtToken = ADDR_USDT_TOKEN;
  if (ADDR_USDT_TOKEN === "" || ADDR_USDT_TOKEN === undefined) {
    const usdtToken = await deployContract("UsdtToken", "UsdtToken", true);
    AddrUsdtToken = getContractAddr(usdtToken);
  }
  const coin = await deployCoin(
    getContractAddr(metaToken),
    getContractAddr(raceToken),
    AddrUsdtToken
  );
  const equipNft = await deployContract("EquipToken", "EquipToken");
  const horseNft = await deployContract("HorseToken", "HorseToken");
  const arenaNft = await deployContract("ArenaToken", "ArenaToken");
  const coin721 = await deployCoin721(
      getContractAddr(equipNft),
      getContractAddr(horseNft),
      getContractAddr(arenaNft)
  );
  await deployMysteryBox(getContractAddr(metaToken), getContractAddr( horseNft), AddrUsdtToken);
  const bonus = await deployBonus(metaToken, horseNft);

  const nftAttr = await deployNFTAttr(
      getContractAddr(equipNft),
      getContractAddr(horseNft),
      getContractAddr(arenaNft)
  );
  const equipAttr = await deployNftAttrOpera(
    "HorseEquipAttrOperaContract",
    "EquipAttr",
      getContractAddr(nftAttr),
      getContractAddr(equipNft)
  );
  const arenaAttr = await deployNftAttrOpera(
    "HorseArenaAttrOperaContract",
    "ArenaAttr",
      getContractAddr(nftAttr),
      getContractAddr(arenaNft)
  );
  const raceAttr1 = await deployNftAttrOpera(
    "HorseRaceAttrOpera1",
    "RaceAttr1",
      getContractAddr(nftAttr),
      getContractAddr(horseNft)
  );
  const raceAttr1_1 = await deployNftAttrOpera(
    "HorseRaceAttrOpera1_1",
    "RaceAttr1_1",
      getContractAddr(nftAttr),
      getContractAddr(horseNft)
  );
  const raceAttr2 = await deployNftAttrOpera(
    "HorseRaceAttrOpera2",
    "RaceAttr2",
      getContractAddr(nftAttr),
      getContractAddr(horseNft)
  );
  const raceAttr2_1 = await deployNftAttrOpera(
    "HorseRaceAttrOpera2_1",
    "RaceAttr2_1",
      getContractAddr(nftAttr),
      getContractAddr(horseNft)
  );

  const raceCourseAttr = await deployRaceCourseAttr();
  const raceCourseAttrOp = await deployRaceCourseAttrOpera(
      getContractAddr(raceCourseAttr)
  );

  const libBytes = await deployContract(
    "contracts/library/Bytes.sol:Bytes",
    "Bytes"
  );
  const contractOptions = {
    libraries: {
      Bytes: getContractAddr(libBytes),
    },
  };
  const emptyContractOptions = {
  };
  await deployEquipExtra(
      await coin.getAddress(),
      coin721,
      getContractAddr(equipAttr),
      getContractAddr(equipNft),
      ADDR_FEE_RECEIVER,
      getContractAddr(constant),
      getContractAddr(raceAttr1_1)
  );
  await deployRaceExtra(
    emptyContractOptions,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(horseNft),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(coin),
    coin721,
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(equipAttr),
      getContractAddr(equipNft)
  );
  await deployRaceExtra1(
    emptyContractOptions,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(horseNft),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(coin),
    coin721,
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(equipAttr),
      getContractAddr(equipNft)
  );
  await deployRaceExtra2(
    contractOptions,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(horseNft),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(coin),
    coin721,
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(equipAttr),
      getContractAddr(equipNft)
  );

  await deployArenaExtra(
    emptyContractOptions,
      getContractAddr(arenaAttr),
      getContractAddr(raceCourseAttrOp),
      getContractAddr(arenaNft),
      getContractAddr(coin),
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(horseNft),
      getContractAddr(bonus.distribute)
  );
  await deployArenaExtra1(
    contractOptions,
      getContractAddr(arenaAttr),
      getContractAddr(raceCourseAttrOp),
      getContractAddr(arenaNft),
      getContractAddr(coin),
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(horseNft),
      getContractAddr(metaToken),
      getContractAddr(raceToken),
      getContractAddr(bonus.distribute)
  );
  await deployArenaExtra2(
    contractOptions,
      getContractAddr(arenaAttr),
      getContractAddr(raceCourseAttrOp),
      getContractAddr(arenaNft),
      getContractAddr(coin),
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(horseNft)
  );
  await deployArenaExtra3(
    contractOptions,
      getContractAddr(arenaAttr),
      getContractAddr(raceCourseAttrOp),
      getContractAddr(arenaNft),
      getContractAddr(coin),
      getContractAddr(constant),
    ADDR_FEE_RECEIVER,
      getContractAddr(raceAttr1),
      getContractAddr(raceAttr1_1),
      getContractAddr(raceAttr2),
      getContractAddr(raceAttr2_1),
      getContractAddr(horseNft)
  );

  await _batchOperate("addMinter", RELY_ON_MINTER_MAP);
  console.log("SetPermission addMinter finished.");

  await _batchOperate("addAdmin", RELY_ON_ADMIN_MAP);
  console.log("SetPermission addAdmin finished.");

  await _batchOperate("addProgram", RELY_ON_PROGRAM_MAP);
  console.log("SetPermission addProgram finished.");

  await _batchOperate("registerTransferHandler", TRANSFER_HANDLER_MAP);
  console.log("SetTransferHandler finished.");

  await _batchOperate("addOfficialContract", OFFICIAL_CONTRACT_MAP);
  console.log("AddOfficial finished.");
}

async function deployConstant() {
  const contract = await deployUpgradeContract("Constant", "Constant", true);
  for (let k in GAME_DISTANCE) {
    await contract.setApplyGameAmount(k, GAME_DISTANCE[k].fee);
    await contract.setAwardGameAmount(k, GAME_DISTANCE[k].award);
  }
  return contract;
}

async function deployCoin(metaToken, raceToken, usdtToken) {
  const contract = await deployUpgradeContract("Coin", "Coin", true);
  const tokenId = [1, 2, 3];
  const tokenAddress = [metaToken, raceToken, usdtToken];
  const tokenPrice = [1, 1, 10000];
  const addTokens = await contract.add(tokenId, tokenAddress, tokenPrice);
  await addTokens.wait();
  console.log("deployCoin: addTokens(" + tokenAddress.join(",") + ")");
  return contract;
}

async function deployCoin721(equipNft, horseNft, arenaNft) {
  const contract = await deployUpgradeContract("Coin721", "Coin721", true);
  const tokenAddress = [equipNft, horseNft, arenaNft];
  const addTokens = await contract.add(tokenAddress);
  await addTokens.wait();
  console.log("deployCoin721: addTokens(" + tokenAddress.join(",") + ")");
  return contract;
}

async function _setEquipFields(attrContract, equipNft) {
  const fields = [
    "horseEquipTypes",
    "horseEquipStyle",
    "horseEquipStatus",
    "horseEquipPrice",
    "horseEquipOfUid",
    "horseEquipOfHorseId",
    "horseEquipDiscount",
    "horseEquipReward",
    "horseEquipCoin",
    "horseEquipLastOwner",
    "horseEquipLastPrice",
    "lastOperaTime",
  ];
  const ftypes = [1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1];
  const fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  const setfield = await attrContract.setFiled(
    equipNft,
    fields,
    ftypes,
    fauths
  );
  await setfield.wait();
}

async function _setHorseFields(attrContract, horseNft) {
  let fields = [
    "horseRaceName",
    "nameUptCount",
    "birthDay",
    "mainGeneration",
    "slaveGeneration",
    "generationScore",
    "gender",
    "color",
    "gene",
    "gripGene",
  ];
  let ftypes = [2, 1, 1, 1, 1, 1, 1, 1, 1, 1];
  let fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  let setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = [
    "accGene",
    "endGene",
    "speedGene",
    "turnToGene",
    "controlGene",
    "trainingValue",
    "trainingTime",
    "useTraTime",
    "energy",
    "energyUpdateTime",
  ];
  ftypes = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
  fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = [
    "gradeScore",
    "gradeIntegral",
    "gradeScoreMark",
    "raceScoreUpdateTime",
    "father",
    "mother",
    "breedCount",
    "breedTime",
    "gradeIntegralYear",
    "gradeIntegralYearTime",
  ];
  ftypes = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
  fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = [
    "gradeIntegralMonth",
    "gradeIntegralMonthTime",
    "gradeIntegralWeek",
    "gradeIntegralWeekTime",
  ];
  ftypes = [1, 1, 1, 1];
  fauths = [2, 2, 2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = [
    "headWearId",
    "armorId",
    "ponytailId",
    "hoofId",
    "grade",
    "raceCount",
    "winCount",
    "raceId",
    "raceType",
    "racecourse",
  ];
  ftypes = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
  fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = [
    "distance",
    "raceUpdateTime",
    "horseRaceStatus",
    "horseRaceDiscount",
    "horseRaceReward",
    "horseRaceCoin",
    "horseRaceLastOwner",
    "horseRaceLastPrice",
    "horseRacePrice",
  ];
  ftypes = [1, 1, 1, 1, 1, 1, 1, 1, 1];
  fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();

  fields = ["sellUpdateTime", "studUpdateTime"];
  ftypes = [1, 1];
  fauths = [2, 2];
  setfield = await attrContract.setFiled(horseNft, fields, ftypes, fauths);
  await setfield.wait();
}

async function _setArenaFields(attrContract, arenaNft) {
  const fields = [
    "name",
    "createTime",
    "ownerType",
    "isClose",
    "raceCount",
    "lastRaceTime",
    "totalRaceCount",
    "mortAmount",
    "gameId",
    "horseIds",
  ];
  const ftypes = [2, 1, 1, 1, 1, 1, 1, 1, 1, 1];
  const fauths = [2, 2, 2, 2, 2, 2, 2, 2, 2, 2];
  const setfield = await attrContract.setFiled(
    arenaNft,
    fields,
    ftypes,
    fauths
  );
  await setfield.wait();
}

async function deployNFTAttr(equipNft, horseNft, arenaNft) {
  const contract = await deployUpgradeContract("ERC721Attr", "RaceNFTAttr", true);
  await _setEquipFields(contract, equipNft);
  await _setHorseFields(contract, horseNft);
  await _setArenaFields(contract, arenaNft);
  return contract;
}

async function deployNftAttrOpera(contractPath, contractName, nftAttr, nft) {
  const contract = await deployUpgradeContract(contractPath, contractName, true);
  const init = await contract.init(nftAttr, nft);
  await init.wait();
  return contract;
}

async function deployRaceCourseAttr() {
  const contract = await deployUpgradeContract("ERC721Attr", "HorseCourseAttr", true);
  return contract;
}

async function deployRaceCourseAttrOpera(raceCourseAttr) {
  const contract = await deployUpgradeContract(
    "RacecourseAttrOperaContract",
    "HorseCourseAttrOpera",
    true
  );
  const init = await contract.init(raceCourseAttr);
  await init.wait();
  return contract;
}

async function deployEquipExtra(
  coin,
  coin721,
  equipAttrOpera,
  equipNft,
  feeAddr,
  constant,
  raceHorseAttr1_1
) {
  const contract = await deployUpgradeContract(
    "HorseEquipContract",
    "EquipExtra",
    true
  );
  const init = await contract.initHorseEquipAttrAddress(
    coin,
      getContractAddr(coin721),
    equipAttrOpera,
    equipNft,
    feeAddr,
    constant,
    raceHorseAttr1_1
  );
  await init.wait();
  return contract;
}

async function deployRaceExtra(
  contractOptions,
  raceAttr1,
  raceAttr1_1,
  horseNft,
  raceAttr2,
  raceAttr2_1,
  coin,
  coin721,
  constant,
  feeAddr,
  equipAttr,
  equipNft
) {
  const contract = await deployUpgradeContract(
    "HorseRaceContract",
    "RaceExtra",
    true,
    undefined,
    contractOptions
  );

  const init = await contract.initHorseRaceAttrAddress(
    raceAttr1,
    raceAttr1_1,
    horseNft,
    raceAttr2,
    raceAttr2_1,
    coin,
    constant,
    feeAddr
  );
  await init.wait();
  return contract;
}

async function deployRaceExtra1(
  contractOptions,
  raceAttr1,
  raceAttr1_1,
  horseNft,
  raceAttr2,
  raceAttr2_1,
  coin,
  coin721,
  constant,
  feeAddr,
  equipAttr,
  equipNft
) {
  const contract = await deployUpgradeContract(
    "HorseRaceExtra1",
    "RaceExtra1",
    true,
    undefined,
    contractOptions
  );

  const init = await contract.initHorseRaceAttrAddress(
    raceAttr1,
    raceAttr1_1,
    raceAttr2,
    raceAttr2_1,
    equipAttr,
    horseNft,
    equipNft,
    constant,
    coin,
    feeAddr
  );
  await init.wait();
  return contract;
}

async function deployRaceExtra2(
  contractOptions,
  raceAttr1,
  raceAttr1_1,
  horseNft,
  raceAttr2,
  raceAttr2_1,
  coin,
  coin721,
  constant,
  feeAddr,
  equipAttr,
  equipNft
) {
  const contract = await deployUpgradeContract("HorseRaceExtra2", "RaceExtra2", true);
  const init = await contract.initHorseRaceAttrAddress(
    raceAttr1,
    raceAttr1_1,
    raceAttr2,
    raceAttr2_1,
    coin,
      getContractAddr(coin721),
    horseNft,
    constant,
    feeAddr
  );
  await init.wait();
  return contract;
}

async function deployArenaExtra(
  contractOptions,
  arenaAttr,
  courseAttr,
  arenaNft,
  coin,
  constant,
  feeAddr,
  raceAttr1,
  raceAttr1_1,
  raceAttr2,
  raceAttr2_1,
  horseNft,
  distribute
) {
  const contract = await deployUpgradeContract(
    "HorseArenaContract",
    "ArenaExtra",
    true,
    undefined,
    contractOptions
  );
  const init = await contract.init(arenaAttr, arenaNft, coin, constant);
  await init.wait();
  return contract;
}

async function deployArenaExtra1(
  contractOptions,
  arenaAttr,
  courseAttr,
  arenaNft,
  coin,
  constant,
  feeAddr,
  raceAttr1,
  raceAttr1_1,
  raceAttr2,
  raceAttr2_1,
  horseNft,
  metaToken,
  raceToken,
  distribute
) {
  const contract = await deployUpgradeContract("HorseArenaExtra", "ArenaExtra1", true);
  const init = await contract.init(
    arenaAttr,
    coin,
    courseAttr,
    raceAttr1,
    raceAttr1_1,
    raceAttr2,
    raceAttr2_1,
    horseNft,
    arenaNft,
    constant,
    metaToken,
    raceToken
  );
  await init.wait();
  await contract.setDistribute(distribute);
  return contract;
}

async function deployArenaExtra2(
  contractOptions,
  arenaAttr,
  courseAttr,
  arenaNft,
  coin,
  constant,
  feeAddr,
  raceAttr1,
  raceAttr1_1,
  raceAttr2,
  raceAttr2_1,
  horseNft
) {
  const contract = await deployUpgradeContract(
    "HorseArenaExtra2",
    "ArenaExtra2",
    true
  );
  const init = await contract.init(
    arenaAttr,
    coin,
    horseNft,
    raceAttr1_1,
    raceAttr2,
    raceAttr2_1,
    constant
  );
  await init.wait();
  return contract;
}

async function deployArenaExtra3(
  contractOptions,
  arenaAttr,
  courseAttr,
  arenaNft,
  coin,
  constant,
  feeAddr,
  raceAttr1,
  raceAttr1_1,
  raceAttr2,
  raceAttr2_1,
  horseNft
) {
  const contract = await deployUpgradeContract(
    "HorseArenaExtra3",
    "ArenaExtra3",
    true
  );

  const init = await contract.init(
    arenaAttr,
    courseAttr,
    raceAttr1_1,
    raceAttr2_1,
    constant
  );
  await init.wait();
  return contract;
}

function _allColor() {
  const colors = [];
  colors.push("FF00FFFF"); // 紫色
  colors.push("FF5A00FF"); // 棕色
  colors.push("00FFFFFF"); // 青色
  colors.push("FFFFFFFF"); // 原色
  return colors;
}

async function mintHorse(horseRaceAddr, toAddr, styleName, start, count) {
  let horseIdx = start;
  const prefix = "MR";
  const horseRaceContract = await hre.ethers.getContractAt(
    "HorseRaceContract",
    horseRaceAddr
  );
  let pCount = BATCH_HORSE_COUNT;
  let total = 0;
  const geneList = HORSE_GENE[styleName];
  const colors = _allColor();

  for (total = 0; total < count; ) {
    if (count - total > BATCH_HORSE_COUNT) {
      pCount = BATCH_HORSE_COUNT;
    } else {
      pCount = count - total;
    }
    console.log("Mint horse count ", pCount);
    const name = [];
    const style = [];
    const mainGeneration = [];
    const slaveGeneration = [];
    const generationScore = [];
    const gender = [];
    const color = [];
    const gene = [];
    const to = [];
    for (let i = 1; i <= pCount; i++, horseIdx++) {
      const cname = prefix + "-" + horseIdx;
      name.push(cname);
      style.push(styleName);
      mainGeneration.push(0);
      slaveGeneration.push(0);
      generationScore.push(10000);
      to.push(toAddr);
      if (i % 2 == 0) {
        gender.push(1); // male
      } else {
        gender.push(0); // female
      }
      const initGene = geneList[i % geneList.length];
      const nColor = colors[i % colors.length];
      color.push(nColor);
      gene.push(initGene);
    }
    const mint = await horseRaceContract.batchMintHorse(
      name,
      style,
      mainGeneration,
      slaveGeneration,
      generationScore,
      gender,
      color,
      gene,
      to
    );
    await mint.wait();
    console.log(
      "Batch mint horse to",
      toAddr,
      styleName,
      total,
      total + pCount
    );

    total += pCount;
  }
  console.log(
    "Mint horse with style ",
    styleName,
    " finished, total ",
    start,
    total
  );
}

async function batchMintHorseToPool(horseRaceAddr) {
  let start = 1;
  for (let i = 0; i < MYSTERY_BOX_HORSE_COUNT.length; i++) {
    let count = 0;
    for (let j = 0; j < MYSTERY_BOX_HORSE_COUNT.length; j++) {
      count +=
        (HORSE_GENE_PROBABILITIES[j][i] * MYSTERY_BOX_HORSE_COUNT[i]) / 10000;
    }
    await mintHorse(
      horseRaceAddr,
      ADDR_MINT_RECEIVER,
      MYSTERY_BOX_HORSE_TYPES[i],
      start,
      count
    );
    start += count;
  }
}

async function mintArena() {
  const accounts = await hre.ethers.getSigners();
  const metaToken = ContractMap.get("MetaToken");
  const coinContract = ContractMap.get("Coin");
  const constantContract = ContractMap.get("Constant");

  const depositAmount = await constantContract.getMortAmount();
  const decimals = await metaToken.decimals();
  // const deposit = depositAmount * 10 ** decimals;
  const deposit = BigInt(depositAmount) * BigInt(10 ** Number(decimals));
  // 购买赛场需要抵押 meta token
  const balance = await metaToken.balanceOf(accounts[0].address);
  console.log("User have meta token %d and need deposit %d.", balance, deposit);
  const approve = await metaToken.approve(getContractAddr(coinContract), balance);
  await approve.wait();

  const signer = process.env.NAME_SIGNER_KEY;
  const arenaName = "MetaRace";
  const hash = hre.ethers.solidityPackedKeccak256(["string"], [arenaName]);
  const signingKey = new hre.ethers.SigningKey(signer);
  const signature = await signingKey.sign(hash);
  // const rawSign = await hre.ethers.joinSignature(signature);
  const rawSign = signature.serialized
  const horseArenaContract = ContractMap.get("ArenaExtra");
  await horseArenaContract.mintOfficialArena(arenaName, rawSign);
  console.log("Mint arena end.");
}

async function mintEquip(count) {
  const equipExtra = ContractMap.get("EquipExtra");
  let lstEquipType = [];
  let lstEquipStyle = [];
  if (count < 0) {
    for (let i = 0; i < -count; i++) {
      lstEquipType.push((i % EQUIP_TYPE_COUNT) + 1);
      lstEquipStyle.push((i % EQUIP_STYLE_COUNT) + 1);
    }
  } else if (count > 0) {
    for (let equipType = 1; equipType <= EQUIP_TYPE_COUNT; equipType++) {
      for (let equipStyle = 1; equipStyle <= EQUIP_STYLE_COUNT; equipStyle++) {
        for (let i = 0; i < count; i++) {
          lstEquipType.push(equipType);
          lstEquipStyle.push(equipStyle);
        }
      }
    }
  }

  if (lstEquipType.length > 0) {
    for (let i = 0; i < lstEquipType.length; i += BATCH_EQUIP_COUNT) {
      let lstType = [];
      let lstStyle = [];
      let to = [];
      let n =
        i + BATCH_EQUIP_COUNT <= lstEquipType.length
          ? BATCH_EQUIP_COUNT
          : lstEquipType.length - i;
      for (let j = 0; j < n; j++) {
        lstType[j] = lstEquipType[i + j];
        lstStyle[j] = lstEquipStyle[i + j];
        to[j] = ADDR_MINT_RECEIVER;
        console.log(
          "Mint equip with type = %d and style = %d.",
          lstType[j],
          lstStyle[j]
        );
      }
      await equipExtra.batchMintEquip(to, lstType, lstStyle);
    }
  }
}

async function addInitColor() {
  /*
  color = "FF00FFFF"; // 紫色
  color = "FF5A00FF"; // 棕色
  color = "00FFFFFF"; // 青色
  color = "FFFFFFFF"; // 原色
  */
  const constant = ContractMap.get("Constant");
  const colorList = _allColor();
  for (const color of colorList) {
    console.log("Added color", color);
    await constant.addColor(color);
  }

  const count = await constant.getInitColorsCount();
  // console.log("got init color count is ", count);
  for (let i = 0; i < count; i++) {
    const color = await constant.getInitColors(i);
    console.log("Check color", color, "at", i);
  }
}

async function _batchOperate(fn, lst) {
  for (let i = 0; i < lst.length; i++) {
    const item = lst[i];
    let contract = ContractMap.get(item.contract);
    if (item.contractPath && item.contractAddr) {
      contract = await getContract(item.contractPath, item.contractAddr);
    }
    const isString = typeof item.params[0] == "string";
    let addr = isString
      ? getContractAddr(ContractMap.get(item.params[0]))
      : item.params[0].address;
    if (addr) {
      let args;
      switch (fn) {
        case "registerTransferHandler":
          args = [addr, addr];
          break;
        default:
          args = [addr];
      }
      await _callContract(
        item.contract,
        contract,
        fn,
        item.params,
        args,
        false
      );
    }
  }
}

async function _callContract(name, contract, fn, params, args, isCall) {
  const isString = typeof params[0] == "string";
  let result;
  switch (fn) {
    case "registerTransferHandler":
      result = await contract[fn].apply(contract, args);
      break;
    default:
      result = await contract[fn].apply(contract, args);
  }
  if (!isCall) {
    await result.wait();
    console.log(
      fn + "(" + name + "," + args[0] + (isString ? "," + params[0] : "") + ")"
    );
  } else {
    console.log(
      fn +
        "(" +
        name +
        "," +
        args[0] +
        (isString ? "," + params[0] : "") +
        ") = " +
        result
    );
  }
  return result;
}

async function _checkOperate(fn, lst, fnSet) {
  for (let i = 0; i < lst.length; i++) {
    const item = lst[i];
    const contract = await getContract(item.contractPath, item.contractAddr);
    const isString = typeof item.params[0] == "string";
    let addr = isString
      ? getContractAddr(ContractMap.get(item.params[0]))
      : item.params[0].address;
    if (addr) {
      const result = await _callContract(
        item.contract,
        contract,
        fn,
        item.params,
        [addr],
        true
      );
      if (!result && fnSet) {
        let args;
        switch (fnSet) {
          case "registerTransferHandler":
            args = [addr, addr];
            break;
          default:
            args = [addr];
        }
        await _callContract(
          item.contract,
          contract,
          fnSet,
          item.params,
          args,
          false
        );
      }
    }
  }
}

async function addOfficial() {
  await _batchOperate("addOfficialContract", OFFICIAL_CONTRACT_MAP);
  console.log("AddOfficial finished.");
}

async function setTransferHandler() {
  await _batchOperate("registerTransferHandler", TRANSFER_HANDLER_MAP);
  console.log("SetTransferHandler finished.");
}

async function checkPrivilege() {
  const accounts = [{ address: ADDR_API_SERVER }];

  const ProgramMap = [
    {
      contractPath: "HorseArenaExtra",
      contractAddr: "0x05a757bf7C1811fC750879b68DC6a8c7bd2D8759",
      contract: "ArenaExtra1",
      params: accounts,
    },
    {
      contractPath: "HorseArenaExtra3",
      contractAddr: "0x9B8D70d2993b15F4DeA04e9C4A22183C05f0ec6f",
      contract: "ArenaExtra3",
      params: accounts,
    },
    {
      contractPath: "HorseRaceExtra1",
      contractAddr: "0xAE154d855c7e0aa9eF3f432A1051518f23167A63",
      contract: "RaceExtra1",
      params: accounts,
    },
  ];
  await _checkOperate("isProgram", ProgramMap, "addProgram");

  const AdminMap = [
    {
      contractPath: "Constant",
      contractAddr: "0x05De106a835E438AA02E103008Cc1dCD9425321d",
      contract: "Constant",
      params: accounts,
    },
  ];
  await _checkOperate("isAdmin", AdminMap, "addAdmin");
}

async function initialSetting() {
  // await modifyConstant(contractMap);
  await _batchOperate("addProgram", API_SERVER_PROGRAM_MAP);
  await _batchOperate("addAdmin", API_SERVER_ADMIN_MAP);
  await addInitColor();
}

function printContractMap() {
  console.log("----- contract address list: ");
  ContractMap.forEach(function (v, k) {
    const contractAddr = getContractAddr(v);
    console.log("ContractAddr:", contractAddr, "for", k);
  });
  console.log("----- contract address list end. ");
}

async function main() {
  if (!ADDR_MINT_RECEIVER) {
    console.log("Please set TOKEN721_RECEIVER in .env file first.");
    return;
  }
  console.log("Initial deploy started");

  // Hardhat always runs the compile task when running scripts with its command
  // line interface.
  //
  // We get the contract list to deploy

  await initialDeploy();
  await initialSetting();
  await mintArena();
  await mintEquip(10);
  console.log("Initial deploy finished");

  printContractMap();

  // await batchMintHorseToPool(ContractMap.get("RaceExtra").address);
  // await addMysteryBoxToken(ContractMap.get("NFTMysteryBoxOffering").address);

  // printContractMap();
}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});