import { JsonFragment } from '@ethersproject/abi';
import { PublicKey } from '@solana/web3.js';
import { randomBytes } from 'crypto';

/** Application Binary Interface of a Solidity contract in JSON form */
export type ABI = JsonFragment[];

/** PDA and the seed used to derive it */
export interface ProgramDerivedAddress {
    address: PublicKey;
    seed: Buffer;
}

/**
 * Create a Program Derived Address from a program ID and a random seed
 *
 * @param program Program ID to derive the PDA using
 *
 * @return PDA and the seed used to derive it
 */
export async function createProgramDerivedAddress(program: PublicKey): Promise<ProgramDerivedAddress> {
    // eslint-disable-next-line no-constant-condition
    while (true) {
        const seed = randomBytes(7);

        let address: PublicKey;
        try {
            address = await PublicKey.createProgramAddress([seed], program);

        } catch (error) {
            // If a valid PDA can't be found using the seed, generate another and try again
            continue;
        }

        return { address, seed };
    }
}

/**
 * Encode a public key as a hexadecimal string
 *
 * @param publicKey Public key to convert
 *
 * @return Hex-encoded public key
 */
export function publicKeyToHex(publicKey: PublicKey): string {
    return '0x' + publicKey.toBuffer().toString('hex');
}

/** @internal */
export type Seed = string | PublicKey | Uint8Array | Buffer;

/** @internal */
export function seedToBuffer(seed: Seed): Buffer {
    if (seed instanceof Buffer) {
        return seed;
    } else if (typeof seed === 'string') {
        return Buffer.from(seed, 'utf-8');
    } else if (seed instanceof PublicKey) {
        return seed.toBuffer();
    } else {
        return Buffer.from(seed);
    }
}

/** @internal */
export function encodeSeeds(seeds: Seed[]): Buffer {
    const buffers = seeds.map(seedToBuffer);

    let length = 1;
    for (const buffer of buffers) {
        length += buffer.length + 1;
    }

    const encoded = Buffer.alloc(length);
    encoded.writeUInt8(buffers.length);

    let offset = 1;
    for (const buffer of buffers) {
        encoded.writeUInt8(buffer.length, offset);
        offset += 1;
        buffer.copy(encoded, offset);
        offset += buffer.length;
    }

    return encoded;
}