// File: CoinBase.sol



pragma solidity ^0.4.18;

import "./Ownable.sol";

/**
 * Base ERC721 token contract
 */
contract CoinBase is Ownable {
  // ERC-721: implementsERC721() public view returns (bool _implementsERC721)
  bool public constant implementsERC721 = true;

  mapping(address => uint256) public coinBalance;
  mapping(uint256 => address) public coinIdToOwner;
  mapping(uint256 => address) public coinIdToApprovedAddress;

  // Events
  event Transfer(address indexed from, address indexed to, uint256 coinId);
  event Approval(address indexed owner, address indexed approved, uint256 coinId);

  // ERC-20 Compatibility: balanceOf(address walletAddress) public view returns (uint256 balance)
  function balanceOf(address walletAddress) public view returns (uint256 balance) {
    return coinBalance[walletAddress];
  }

  // Basic ownership: ownerOf(uint256 coinId) public view returns (address walletAddress)
  function ownerOf(uint256 coinId) public view returns (address walletAddress) {
    return coinIdToOwner[coinId];
  }

  // Basic ownership: approve(address newCoinOwner, uint256 coinId) public
  function approve(address newCoinOwner, uint256 coinId) public {
    require(coinIdToOwner[coinId] == msg.sender);
    coinIdToApprovedAddress[coinId] = newCoinOwner;
    Approval(msg.sender, newCoinOwner, coinId);
  }

  // Basic ownership: approveTransfer(address newCoinOwner, uint256 coinId) public
  function approveTransfer(address newCoinOwner, uint256 coinId) public {
    approve(newCoinOwner, coinId);
  }

  // Basic ownership: approvedFor(uint256 coinId) public view returns (address approvedWallet)
  function approvedFor(uint256 coinId) public view returns (address approvedWallet) {
    return coinIdToApprovedAddress[coinId];
  }

  // Basic ownership: getApproved(uint256 coinId) public view returns (address approvedWallet)
  function getApproved(uint256 coinId) public view returns (address approvedWallet) {
    return approvedFor(coinId);
  }

  // Basic ownership: takeOwnership(uint256 coinId) public
  function takeOwnership(uint256 coinId) public {
    require(coinIdToApprovedAddress[coinId] == msg.sender);
    address _from = coinIdToOwner[coinId];

    _transfer(_from, msg.sender, coinId);
  }

  // Basic ownership: transferFrom(address currentCoinOwner, address newCoinOwner, uint256 coinId) public
  function transferFrom(address currentCoinOwner, address newCoinOwner, uint256 coinId) public {
    require(newCoinOwner != address(0));
    require(newCoinOwner != address(this));
    require(coinIdToApprovedAddress[coinId] == msg.sender);
    require(coinIdToOwner[coinId] == currentCoinOwner);

    _transfer(currentCoinOwner, newCoinOwner, coinId);
  }

  // Basic ownership: transfer(address newCoinOwner, uint256 coinId) public
  function transfer(address newCoinOwner, uint256 coinId) public {
    require(newCoinOwner != address(0));
    require(newCoinOwner != address(this));
    require(coinIdToOwner[coinId] == msg.sender);

    _transfer(msg.sender, newCoinOwner, coinId);
  }

  // Basic ownership: _transfer(address currentCoinOwner, address newCoinOwner, uint256 coinId) internal
  function _transfer(address currentCoinOwner, address newCoinOwner, uint256 coinId) internal {
    if (coinIdToOwner[coinId] != address(0)) {
      delete coinIdToApprovedAddress[coinId];
      coinBalance[currentCoinOwner] -= 1;
    }
    coinIdToOwner[coinId] = newCoinOwner;
    coinBalance[newCoinOwner] += 1;
    Transfer(currentCoinOwner, newCoinOwner, coinId);
  }
}


// File: Ownable.sol



pragma solidity ^0.4.18;

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */

contract Ownable {
  address public owner;
  address public scriptAddress;

  /**
   * @dev The Ownable constructor sets the original `owner` of the contract to the sender
   * account.
   */
  function Ownable() public {
    owner = msg.sender;
    scriptAddress = address(0);
  }

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  /**
   * @dev Throws if called by any account other than the owner or script runner.
   */
  modifier onlyOwnerOrScript() {
    require(msg.sender == owner || msg.sender == scriptAddress);
    _;
  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != address(0));
    owner = newOwner;
  }

  /**
   * @dev Allows the current owner to transfer script control of the contract to a newScriptAddress.
   * @param newScriptAddress The address to transfer script ownership to.
   */
  function setScriptAddress(address newScriptAddress) public onlyOwner {
    scriptAddress = newScriptAddress;
  }
}

// File: TheValentineCoin.sol



pragma solidity ^0.4.18;

import "./Ownable.sol";
import "./CoinBase.sol";
import "./TheValentineCoinBase.sol";
import "./TheValentineCoinAdministration.sol";


contract TheValentineCoin is TheValentineCoinAdministration {
  uint256 public constant coinPrice = 33 finney;

  event ReservedCoin(address to);

  function () public payable {
    require(reservationActive == true);
    require(msg.value >= coinPrice);
    ReservedCoin(msg.sender);
  }

  function distributeCoin(uint256 coinId, address newCoinOwner, string coinEngraving) public onlyOwnerOrScript {
    if (bytes(coinEngraving).length != 0) {
      engravings[coinId] = coinEngraving;
    }
    _transfer(owner, newCoinOwner, coinId);
  }

  function destruct() public onlyOwner {
    selfdestruct(owner);
  }
}

// File: TheValentineCoinAdministration.sol



pragma solidity ^0.4.18;

import "./Ownable.sol";
import "./CoinBase.sol";
import "./TheValentineCoinBase.sol";


contract TheValentineCoinAdministration is TheValentineCoinBase {
  bool public reservationActive;

  function TheValentineCoinAdministration() public {
    reservationActive = true;
  }

  // Administration: emergencyCoinErasure(uint256 coinId, string erasureReason) public onlyOwner
  function emergencyCoinErasure(uint256 coinId, string erasureReason) public onlyOwner {
    require(bytes(engravings[coinId]).length != 0);
    engravings[coinId] = erasureReason;
  }

  // Administration: sendMoneyToScript() public onlyOwner
  function sendMoneyToScript() public onlyOwner {
    require(scriptAddress != address(0));
    require(this.balance >= 33 finney);
    scriptAddress.transfer(33 finney);
  }

  // Administration: toggleReservationState() public onlyOwnerOrScript
  function toggleReservationState() public onlyOwnerOrScript {
    reservationActive = !reservationActive;
  }

  // Administration: withdrawFunds() public onlyOwner
  function withdrawFunds() public onlyOwner {
    owner.transfer(this.balance);
  }
}

// File: TheValentineCoinBase.sol



pragma solidity ^0.4.18;

import "./Ownable.sol";
import "./CoinBase.sol";


/**
 * ValentineCoin specific token methods contract
 */
contract TheValentineCoinBase is CoinBase {
  // Constants
  // ERC-20 Compatibility: name() constant returns (string name)
  string public constant name = "The Valentine Coin";
  // ERC-20 Compatibility: symbol() constant returns (string symbol)
  string public constant symbol = "VALENTINE";
  // ERC-20 Compatibility: totalSupply() constant returns (uint256 _totalSupply)
  uint256 public constant totalSupply = 33333;

  mapping(uint256 => string) public engravings;

  function engravingOf(uint256 coinId) public view returns (string coinEngraving) {
    return engravings[coinId];
  }

  // Engrave
  function engrave(uint256 coinId, string coinEngraving) public {
    require(ownerOf(coinId) == msg.sender);
    require(bytes(engravings[coinId]).length == 0);
    require(bytes(coinEngraving).length > 0);
    engravings[coinId] = coinEngraving;
  }
}