import 'solana';

// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L449
// Solidity does not support Rust Option<> type, so we need to handle it manually
// Requires creating a struct for each combination of Option<> types
// If bool for Option<> type is false, comment out the corresponding struct field otherwise instruction fails with "invalid account data"
// TODO: figure out better way to handle Option<> types
library MplMetadata {
	address constant systemAddress = address"11111111111111111111111111111111";

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/instruction/metadata.rs#L31
	struct CreateMetadataAccountArgsV3 {
        DataV2 data;
        bool isMutable;
        bool collectionDetailsPresent; // To handle Rust Option<> in Solidity
        // CollectionDetails collectionDetails;
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/data.rs#L22
    struct DataV2 {
        string name;
        string symbol;
        string uri;
        uint16 sellerFeeBasisPoints;
        bool creatorsPresent; // To handle Rust Option<> in Solidity
        // Creator[] creators;
        bool collectionPresent; // To handle Rust Option<> in Solidity
        // Collection collection;
        bool usesPresent; // To handle Rust Option<> in Solidity
        // Uses uses;
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L10
    struct Creator {
        address creatorAddress;
        bool verified;
        uint8 share;
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L66
    struct Collection {
        bool verified;
        address key;
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state/collection.rs#L57
    struct CollectionDetails {
        CollectionDetailsType detailType;
        uint64 size;
    }
    enum CollectionDetailsType {
        V1
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L43
    struct Uses {
        UseMethod useMethod;
        uint64 remaining;
        uint64 total;
    }

	// Reference: https://github.com/metaplex-foundation/metaplex-program-library/blob/master/bubblegum/program/src/state/metaplex_adapter.rs#L35
    enum UseMethod {
        Burn,
        Multiple,
        Single
    }

	function create_metadata_account(
		address metadata,
		address mint,
		address mintAuthority,
		address payer,
		address updateAuthority,
		string name,
		string symbol,
		string uri,
        address metadataProgramId,
        address rentAddress
	) public {
        // // Example of how to add a Creator[] array to the DataV2 struct
		// Creator[] memory creators = new Creator[](1);
        // creators[0] = Creator({
        //     creatorAddress: payer,
        //     verified: false,
        //     share: 100
        // });

        DataV2 data = DataV2({
            name: name,
            symbol: symbol,
            uri: uri,
            sellerFeeBasisPoints: 0,
            creatorsPresent: false,
             // creators: creators,
            collectionPresent: false,
            // collection: Collection({
            //     verified: false,
            //     key: address(0)
            // }),
            usesPresent: false
            // uses: Uses({
            //     useMethod: UseMethod.Burn,
            //     remaining: 0,
            //     total: 0
            // })
        });

        CreateMetadataAccountArgsV3 args = CreateMetadataAccountArgsV3({
            data: data,
            isMutable: true,
            collectionDetailsPresent: false
			// collectionDetails: CollectionDetails({
            //     detailType: CollectionDetailsType.V1,
            //     size: 0
            // })
        });

        AccountMeta[7] metas = [
            AccountMeta({pubkey: metadata, is_writable: true, is_signer: false}),
            AccountMeta({pubkey: mint, is_writable: false, is_signer: false}),
            AccountMeta({pubkey: mintAuthority, is_writable: false, is_signer: true}),
            AccountMeta({pubkey: payer, is_writable: true, is_signer: true}),
            AccountMeta({pubkey: updateAuthority, is_writable: false, is_signer: false}),
            AccountMeta({pubkey: systemAddress, is_writable: false, is_signer: false}),
            AccountMeta({pubkey: rentAddress, is_writable: false, is_signer: false})
        ];

        bytes1 discriminator = 33;
        bytes instructionData = abi.encode(discriminator, args);

        metadataProgramId.call{accounts: metas}(instructionData);
    }
}
