import { TransactionInstruction } from "@solana/web3.js";
import {
  MultisigInstruction,
  MultisigInstructionProgram,
  UNRECOGNIZED_INSTRUCTION,
} from ".";
import { AnchorAccounts } from "./anchor";
import * as BufferLayout from "@solana/buffer-layout";

// Source: https://docs.rs/solana-program/latest/src/solana_program/loader_upgradeable_instruction.rs.html
export class BpfUpgradableLoaderInstruction implements MultisigInstruction {
  readonly program = MultisigInstructionProgram.BpfUpgradableLoader;
  readonly name: string;
  readonly args: { [key: string]: any };
  readonly accounts: AnchorAccounts;

  constructor(
    name: string,
    args: { [key: string]: any },
    accounts: AnchorAccounts
  ) {
    this.name = name;
    this.args = args;
    this.accounts = accounts;
  }

  static fromTransactionInstruction(
    instruction: TransactionInstruction
  ): BpfUpgradableLoaderInstruction {
    try {
      const instructionTypeLayout = BufferLayout.u32("instruction");
      const typeIndex = instructionTypeLayout.decode(instruction.data);
      switch (typeIndex) {
        case 3:
          return new BpfUpgradableLoaderInstruction(
            "Upgrade",
            {},
            {
              named: {
                programData: instruction.keys[0],
                program: instruction.keys[1],
                buffer: instruction.keys[2],
                spill: instruction.keys[3],
                rent: instruction.keys[4],
                clock: instruction.keys[5],
                upgradeAuthority: instruction.keys[6],
              },
              remaining: instruction.keys.slice(7),
            }
          );
        case 4:
          return new BpfUpgradableLoaderInstruction(
            "SetAuthority",
            {},
            {
              named: {
                programData: instruction.keys[0],
                currentAuthority: instruction.keys[1],
                newAuthority: instruction.keys[2],
              },
              remaining: instruction.keys.slice(3),
            }
          );
        case 5:
          let args;
          // Close instruction supports closing two types of accounts:
          // - A program which takes 4 keys (programData, spill, upgradeAuthority, program)
          // - A buffer which takes 3 keys (buffer, spill, upgradeAuthority)
          if (instruction.keys.length >= 4) {
            args = {
              named: {
                programData: instruction.keys[0],
                spill: instruction.keys[1],
                upgradeAuthority: instruction.keys[2],
                program: instruction.keys[3],
              },
              remaining: instruction.keys.slice(4),
            };
          } else {
            args = {
              named: {
                buffer: instruction.keys[0],
                spill: instruction.keys[1],
                upgradeAuthority: instruction.keys[2],
              },
              remaining: instruction.keys.slice(3),
            };
          }

          return new BpfUpgradableLoaderInstruction("Close", {}, args);
        default: // Many more cases are not supported
          throw Error("Not implemented");
      }
    } catch {
      return new BpfUpgradableLoaderInstruction(
        UNRECOGNIZED_INSTRUCTION,
        { data: instruction.data },
        { named: {}, remaining: instruction.keys }
      );
    }
  }
}
