contract BossyBears is ERC721, Ownable, ReentrancyGuard {
    
    using SafeMath for uint256;
    using Address for address;
    using Strings for uint256;
    
    string public constant PROVENANCE = ""; // Change Provenance
    uint256 public constant MAX_SUPPLY = 11111;
    uint256 public constant MAX_RESERVE = 611;
    uint256 public reserveRemain;    
    uint256 public bearPrice;

    mapping(uint256 => string) private _tokenNames;
    mapping(string => bool) private _namesUsed;
    mapping(address => bool) public premintAccess;

    bool public isPreMintActive;
    bool public isActive;

    event Named(uint256 indexed index, string name);

    constructor() public ERC721("BossyBearClub", "BBC") {
        isActive = false;
        isPreMintActive = false;
        reserveRemain = MAX_RESERVE;
        bearPrice = 0.045 ether;
    }

    function mint(uint256 qty) public payable nonReentrant {
        require(isActive, "!active");
        require(qty <= 100, "qty>$(MAX_QTY)");
        require((totalSupply() + qty) <= (MAX_SUPPLY), "qty>supply");
        require(msg.value >= bearPrice.mul(qty), "Insufficient ether for minting.");
        for (uint256 i = 0; i < qty; i++) {
            uint256 mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
    }

    function preMint(uint256 qty) public payable nonReentrant {
        require(isPreMintActive,"Premint inactive!");
        require(premintAccess[msg.sender], "Premint access not granted!");
        require(qty <= 100, "qty>$(MAX_QTY)");
        require((totalSupply() + qty) <= (MAX_SUPPLY), "qty>supply");
        require(msg.value >= bearPrice.mul(qty), "Insufficient ether for minting.");
        for (uint256 i = 0; i < qty; i++) {
            uint256 mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
    }

    function tokensOfOwner(address _owner)
        public
        view
        returns (uint256[] memory)
    {
        uint256 count = balanceOf(_owner);
        uint256[] memory result = new uint256[](count);
        for (uint256 index = 0; index < count; index++) {
            result[index] = tokenOfOwnerByIndex(_owner, index);
        }
        return result;
    }

    function salePrice() public view returns (uint256) {
        return bearPrice;
    }

    function mintPrice(uint256 qty) public view returns (uint256) {
        return bearPrice.mul(qty);
    }

    /*
    -------------------------------------
    NAMING
    -------------------------------------
    */
    function setName(uint256 tokenId, string memory name) public {
        address owner = ownerOf(tokenId);
        require(msg.sender == owner, "!token.owner");
        require(validateName(name) == true, "!name.valid");
        require(isNameUsed(name) == false, "name.used");
        if (bytes(_tokenNames[tokenId]).length > 0) {
            _namesUsed[toLower(_tokenNames[tokenId])] = false;
        }
        _namesUsed[toLower(name)] = true;
        _tokenNames[tokenId] = name;
        emit Named(tokenId, name);
    }

    function tokenNameByIndex(uint256 index)
        public
        view
        returns (string memory)
    {
        return _tokenNames[index];
    }

    function isNameUsed(string memory nameString) public view returns (bool) {
        return _namesUsed[toLower(nameString)];
    }

    function validateName(string memory str) public pure returns (bool) {
        bytes memory b = bytes(str);
        if (b.length < 3) return false;
        if (b.length > 32) return false;
        if (b[0] == 0x20) return false;
        if (b[b.length - 1] == 0x20) return false;
        bytes1 lastChar = b[0];
        for (uint256 i; i < b.length; i++) {
            bytes1 char = b[i];
            if (char == 0x20 && lastChar == 0x20) return false;
            if (
                !(char >= 0x41 && char <= 0x5A) &&
                !(char >= 0x61 && char <= 0x7A) &&
                !(char == 0x20)
            ) return false;
            lastChar = char;
        }
        return true;
    }

    function toLower(string memory str) public pure returns (string memory) {
        bytes memory bStr = bytes(str);
        bytes memory bLower = new bytes(bStr.length);
        for (uint256 i = 0; i < bStr.length; i++) {
            if ((uint8(bStr[i]) >= 65) && (uint8(bStr[i]) <= 90)) {
                bLower[i] = bytes1(uint8(bStr[i]) + 32);
            } else {
                bLower[i] = bStr[i];
            }
        }
        return string(bLower);
    }

    // Receive ether function
    receive() external payable { } 

    /*
    -------------------------------------
    ADMIN
    -------------------------------------
    */

    function grantPremintAccess(address[] memory premintAddressList) external onlyOwner {
        for (uint256 i = 0; i < premintAddressList.length; i++) {
            premintAccess[premintAddressList[i]] = true;
        } 
    }

    function setActive(bool val) external onlyOwner {
        isActive = val;
    }

    function setPreMintActive(bool val) external onlyOwner {
        isPreMintActive = val;
    }

    function setBaseURI(string memory str) external onlyOwner {
        _setBaseURI(str);
    }

    function setBearPrice(uint256 price) external onlyOwner {
        bearPrice = price;
    }

    function reserve(uint256 qty) external onlyOwner {
        require(qty <= reserveRemain, "qty>reserve_remain");
        require((totalSupply() + qty) <= MAX_SUPPLY, "qty>supply");
        reserveRemain = reserveRemain.sub(qty);
        for (uint256 i = 0; i < qty; i++) {
            uint256 mintIndex = totalSupply();
            _safeMint(msg.sender, mintIndex);
        }
    }

    function NFTgiveaway(address[] memory _to) external onlyOwner {
        require( _to.length <= reserveRemain, "qty>reserve_remain");
        require((totalSupply() +  _to.length) <= MAX_SUPPLY, "qty>supply");
        reserveRemain = reserveRemain.sub(_to.length);
        for (uint256 i = 0; i < _to.length; i++) {
            uint256 mintIndex = totalSupply();
            _safeMint(_to[i], mintIndex);
        }
    }

    function withdraw() external onlyOwner {
        payable(msg.sender).transfer(address(this).balance);
    }
}
function grantPremintAccess(address[] memory premintAddressList) external onlyOwner {
    for (uint256 i = 0; i < premintAddressList.length; i++) {
        premintAccess[premintAddressList[i]] = true;
    } 
}

/*
 -------------------------------------
 ADMIN
 -------------------------------------
 */