use vm::database::MemoryBackingStore;
use vm::types::{TypeSignature, QualifiedContractIdentifier};
use vm::ast::parse;
use vm::analysis::errors::CheckErrors;
use vm::analysis::{AnalysisDatabase, mem_type_check};
use std::convert::TryInto;

fn buff_type(size: u32) -> TypeSignature {
    TypeSignature::BufferType(size.try_into().unwrap()).into()
}

const FIRST_CLASS_TOKENS: &str = "(define-fungible-token stackaroos)
         (define-non-fungible-token stacka-nfts (buff 10))
         (nft-get-owner? stacka-nfts \"1234567890\" )
         (define-read-only (my-ft-get-balance (account principal))
            (ft-get-balance stackaroos account))
         (define-public (my-token-transfer (to principal) (amount uint))
            (ft-transfer? stackaroos amount tx-sender to))
         (define-public (faucet)
           (let ((original-sender tx-sender))
             (as-contract (ft-transfer? stackaroos u1 tx-sender original-sender))))
         (define-public (mint-after (block-to-release uint))
           (if (>= block-height block-to-release)
               (faucet)
               (err u8)))
         (begin (ft-mint? stackaroos u10000 'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR)
                (ft-mint? stackaroos u200 'SM2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQVX8X0G)
                (ft-mint? stackaroos u4 .tokens))";

const ASSET_NAMES: &str =
        "(define-constant burn-address 'SP000000000000000000002Q6VF78)
         (define-private (price-function (name uint))
           (if (< name u100000) u1000 u100))

         (define-non-fungible-token names uint)
         (define-map preorder-map
           ((name-hash (buff 20)))
           ((buyer principal) (paid uint)))

         (define-public (preorder
                        (name-hash (buff 20))
                        (name-price uint))
           (let ((xfer-result (contract-call? .tokens my-token-transfer
                                burn-address name-price)))
            (unwrap! xfer-result (err false))
            (if (map-insert preorder-map
                  (tuple (name-hash name-hash))
                  (tuple (paid name-price) (buyer tx-sender)))
                (ok true)
                (err false))))

         (define-public (register
                        (recipient-principal principal)
                        (name uint)
                        (salt uint))
           (let ((preorder-entry
                   ;; preorder entry must exist!
                   (unwrap! (map-get? preorder-map
                                  (tuple (name-hash (hash160 (xor name salt))))) (err u5)))
                 (name-entry
                   (nft-get-owner? names name)))
             (if (and
                  (is-none name-entry)
                  ;; preorder must have paid enough
                  (<= (price-function name)
                      (get paid preorder-entry))
                  ;; preorder must have been the current principal
                  (is-eq tx-sender
                       (get buyer preorder-entry)))
                  (if (and
                    (is-ok (nft-mint? names name recipient-principal))
                    (map-delete preorder-map
                      (tuple (name-hash (hash160 (xor name salt))))))
                    (ok u0)
                    (err u3))
                  (err u4))))";

#[test]
fn test_names_tokens_contracts() {
    use vm::analysis::type_check;

    let tokens_contract_id = QualifiedContractIdentifier::local("tokens").unwrap();
    let names_contract_id = QualifiedContractIdentifier::local("names").unwrap();

    let mut tokens_contract = parse(&tokens_contract_id, FIRST_CLASS_TOKENS).unwrap();
    let mut names_contract = parse(&names_contract_id, ASSET_NAMES).unwrap();
    let mut marf = MemoryBackingStore::new();
    let mut db = marf.as_analysis_db();

    db.execute(|db| {
        type_check(&tokens_contract_id, &mut tokens_contract, db, true)?;
        type_check(&names_contract_id, &mut names_contract, db, true)
    }).unwrap();
}


#[test]
fn test_bad_asset_usage() {
    use vm::analysis::type_check;

    let bad_scripts = ["(ft-get-balance stackoos tx-sender)",
                       "(ft-get-balance u1234 tx-sender)",
                       "(ft-get-balance 1234 tx-sender)",
                       "(ft-get-balance stackaroos u100)",
                       "(ft-get-balance stackaroos 100)",
                       "(nft-get-owner? u1234 \"abc\")",
                       "(nft-get-owner? stackoos \"abc\")",
                       "(nft-get-owner? stacka-nfts u1234 )",
                       "(nft-get-owner? stacka-nfts \"123456789012345\" )",
                       "(nft-mint? u1234 \"abc\" tx-sender)",
                       "(nft-mint? stackoos \"abc\" tx-sender)",
                       "(nft-mint? stacka-nfts u1234 tx-sender)",
                       "(nft-mint? stacka-nfts \"123456789012345\" tx-sender)",
                       "(nft-mint? stacka-nfts \"abc\" u2)",
                       "(ft-mint? stackoos u1 tx-sender)",
                       "(ft-mint? u1234 u1 tx-sender)",
                       "(ft-mint? stackaroos u2 u100)",
                       "(ft-mint? stackaroos true tx-sender)",
                       "(nft-transfer? u1234 \"a\" tx-sender tx-sender)",
                       "(nft-transfer? stackoos    \"a\" tx-sender tx-sender)",
                       "(nft-transfer? stacka-nfts \"a\" u2 tx-sender)",
                       "(nft-transfer? stacka-nfts \"a\" tx-sender u2)",
                       "(nft-transfer? stacka-nfts u2 tx-sender tx-sender)",
                       "(ft-transfer? stackoos u1 tx-sender tx-sender)",
                       "(ft-transfer? u1234 u1 tx-sender tx-sender)",
                       "(ft-transfer? stackaroos u2 u100 tx-sender)",
                       "(ft-transfer? stackaroos true tx-sender tx-sender)",
                       "(ft-transfer? stackaroos u2 tx-sender u100)",
                       "(define-fungible-token stackaroos true)",
                       "(define-non-fungible-token stackaroos integer)",
                       "(ft-mint? stackaroos 100 tx-sender)",
                       "(ft-transfer? stackaroos 1 tx-sender tx-sender)",
    ];

    let expected = [
        CheckErrors::NoSuchFT("stackoos".to_string()),
        CheckErrors::BadTokenName,
        CheckErrors::BadTokenName,
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::IntType),
        CheckErrors::BadTokenName,
        CheckErrors::NoSuchNFT("stackoos".to_string()),
        CheckErrors::TypeError(buff_type(10),
                               TypeSignature::UIntType),
        CheckErrors::TypeError(buff_type(10),
                               buff_type(15)),
        CheckErrors::BadTokenName,
        CheckErrors::NoSuchNFT("stackoos".to_string()),
        CheckErrors::TypeError(buff_type(10),
                               TypeSignature::UIntType),
        CheckErrors::TypeError(buff_type(10),
                               buff_type(15)),
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::NoSuchFT("stackoos".to_string()),
        CheckErrors::BadTokenName,
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(TypeSignature::UIntType,
                               TypeSignature::BoolType),
        CheckErrors::BadTokenName,
        CheckErrors::NoSuchNFT("stackoos".to_string()),
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(buff_type(10),
                               TypeSignature::UIntType),
        CheckErrors::NoSuchFT("stackoos".to_string()),
        CheckErrors::BadTokenName,
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(TypeSignature::UIntType,
                               TypeSignature::BoolType),
        CheckErrors::TypeError(TypeSignature::PrincipalType,
                               TypeSignature::UIntType),
        CheckErrors::TypeError(TypeSignature::UIntType,
                               TypeSignature::BoolType),
        CheckErrors::DefineNFTBadSignature.into(),
        CheckErrors::TypeError(TypeSignature::UIntType,
                               TypeSignature::IntType),
        CheckErrors::TypeError(TypeSignature::UIntType,
                               TypeSignature::IntType),
    ];

    for (script, expected_err) in bad_scripts.iter().zip(expected.iter()) {
        let tokens_contract = format!("{}\n{}", FIRST_CLASS_TOKENS, script);
        let actual_err = mem_type_check(&tokens_contract).unwrap_err();

        assert_eq!(&actual_err.err, expected_err);
    }
}
