contract ContractManagementSystem is Ownable {
  event UpgradedContract (uint256 contractIdentifier, address indexed oldContractAddress, address indexed newContractAddress);
  event RollbackedContract (uint256 contractIdentifier, address indexed fromContractAddress, address indexed toContractAddress);

  mapping (uint256 => mapping (address => bool)) public managedContracts;
  mapping (uint256 => address) public activeContracts;
  mapping (uint256 => bool) migrationLocks;

  /**
   * @dev Ensure no locks are in place for the given contract identifier.
   * @param contractIdentifier uint256
   */
  modifier onlyWithoutLock(uint256 contractIdentifier) {
    require(!migrationLocks[contractIdentifier]);
    _;
  }

  /**
   * @dev    Get the address of the active contract for the given identifier.
   * @param  contractIdentifier uint256
   * @return address
   */
  function getActiveContractAddress(uint256 contractIdentifier)
    public
    constant
    onlyWithoutLock(contractIdentifier)
    returns (address activeContract)
  {
    // Validate the function arguments.
    require(contractIdentifier != 0x0);
    
    // Get the active contract for the given identifier.
    activeContract = activeContracts[contractIdentifier];

    // Ensure the address is set and the contract is active.
    require(activeContract != 0x0 && Activatable(activeContract).active());
  }

  /**
   * @dev    Check if the contract for the given address is managed.
   * @param  contractIdentifier uint256
   * @param  contractAddress    address
   * @return bool
   */
  function existsManagedContract(uint256 contractIdentifier, address contractAddress)
    public
    constant
    returns (bool)
  {
    // Validate the function arguments.
    require(contractIdentifier != 0x0 && contractAddress != 0x0);

    return managedContracts[contractIdentifier][contractAddress];
  }

  /**
   * @dev    Upgrade the contract for the given contract identifier to a newer version.
   * @dev    investigate potential race condition
   * @param  contractIdentifier uint256
   * @param  newContractAddress address
   */
  function upgradeContract(uint256 contractIdentifier, address newContractAddress)
    public
    onlyOwner
    onlyWithoutLock(contractIdentifier)
  {
    // Validate the function arguments.
    require(contractIdentifier != 0x0 && newContractAddress != 0x0);
    
    // Lock the contractIdentifier.
    migrationLocks[contractIdentifier] = true;

    // New contract should not be active.
    require(!Activatable(newContractAddress).active());

    // New contract should match the given contractIdentifier.
    require(contractIdentifier == Versionable(newContractAddress).identifier());

    // Ensure the new contract is not already managed.
    require (!existsManagedContract(contractIdentifier, newContractAddress));

    // Get the old contract address.
    address oldContractAddress = activeContracts[contractIdentifier];

    // Ensure the old contract is not deactivated already.
    if (oldContractAddress != 0x0) {
      require(Activatable(oldContractAddress).active());
    }

    // Swap the states.
    swapContractsStates(contractIdentifier, newContractAddress, oldContractAddress);

    // Add it to the managed ones.
    managedContracts[contractIdentifier][newContractAddress] = true;

    // Unlock the contractIdentifier.
    migrationLocks[contractIdentifier] = false;
    
    // Trigger event.
    UpgradedContract(contractIdentifier, oldContractAddress, newContractAddress);
  }

  /**
   * @dev Rollback the contract for the given contract identifier to the provided version.
   * @dev investigate potential race condition
   * @param  contractIdentifier uint256
   * @param  toContractAddress  address
   */
  function rollbackContract(uint256 contractIdentifier, address toContractAddress)
    public
    onlyOwner
    onlyWithoutLock(contractIdentifier)
  {
    // Validate the function arguments.
    require(contractIdentifier != 0x0 && toContractAddress != 0x0);

    // Lock the contractIdentifier.
    migrationLocks[contractIdentifier] = true;

    // To contract should match the given contractIdentifier.
    require(contractIdentifier == Versionable(toContractAddress).identifier());

    // Rollback "to" contract should be managed and inactive.
    require (!Activatable(toContractAddress).active() && existsManagedContract(contractIdentifier, toContractAddress));

    // Get the rollback "from" contract for given identifier. Will fail if there is no active contract.
    address fromContractAddress = activeContracts[contractIdentifier];

    // Swap the states.
    swapContractsStates(contractIdentifier, toContractAddress, fromContractAddress);

    // Unlock the contractIdentifier.
    migrationLocks[contractIdentifier] = false;

    // Trigger event.
    RollbackedContract(contractIdentifier, fromContractAddress, toContractAddress);
  }
  
  /**
   * @dev Swap the given contracts states as defined:
   *        - newContractAddress will be activated
   *        - oldContractAddress will be deactived
   * @param  contractIdentifier uint256
   * @param  newContractAddress address
   * @param  oldContractAddress address
   */
  function swapContractsStates(uint256 contractIdentifier, address newContractAddress, address oldContractAddress) internal {
    // Deactivate the old contract.
    if (oldContractAddress != 0x0) {
      Activatable(oldContractAddress).deactivate();
    }

    // Activate the new contract.
    Activatable(newContractAddress).activate();

     // Set the new contract as the active one for the given identifier.
    activeContracts[contractIdentifier] = newContractAddress;
  }
}
function rollbackContract(uint256 contractIdentifier, address toContractAddress)
  public
  onlyOwner
  onlyWithoutLock(contractIdentifier)
{
  // Validate the function arguments.
  require(contractIdentifier != 0x0 && toContractAddress != 0x0);

  // Lock the contractIdentifier.
  migrationLocks[contractIdentifier] = true;

  // To contract should match the given contractIdentifier.
  require(contractIdentifier == Versionable(toContractAddress).identifier());

  // Rollback "to" contract should be managed and inactive.
  require (!Activatable(toContractAddress).active() && existsManagedContract(contractIdentifier, toContractAddress));

  // Get the rollback "from" contract for given identifier. Will fail if there is no active contract.
  address fromContractAddress = activeContracts[contractIdentifier];

  // Swap the states.
  swapContractsStates(contractIdentifier, toContractAddress, fromContractAddress);

  // Unlock the contractIdentifier.
  migrationLocks[contractIdentifier] = false;

  // Trigger event.
  RollbackedContract(contractIdentifier, fromContractAddress, toContractAddress);
}

/**
 * @dev Rollback the contract for the given contract identifier to the provided version.
 * @dev investigate potential race condition
 * @param  contractIdentifier uint256
 * @param  toContractAddress  address
 */