contract Multiownable {

  uint256 public ownersGeneration;
  uint256 public howManyOwnersDecide;
  address[] public owners;
  bytes32[] public allOperations;
  address internal insideCallSender;
  uint256 internal insideCallCount;

  // Reverse lookup tables for owners and allOperations
  mapping(address => uint) public ownersIndices;
  mapping(bytes32 => uint) public allOperationsIndicies;

  // Owners voting mask per operations
  mapping(bytes32 => uint256) public votesMaskByOperation;
  mapping(bytes32 => uint256) public votesCountByOperation;

  event OwnershipTransferred(address[] previousOwners, uint howManyOwnersDecide, address[] newOwners, uint newHowManyOwnersDecide);
  event OperationCreated(bytes32 operation, uint howMany, uint ownersCount, address proposer);
  event OperationUpvoted(bytes32 operation, uint votes, uint howMany, uint ownersCount, address upvoter);
  event OperationPerformed(bytes32 operation, uint howMany, uint ownersCount, address performer);
  event OperationDownvoted(bytes32 operation, uint votes, uint ownersCount,  address downvoter);
  event OperationCancelled(bytes32 operation, address lastCanceller);

  function isOwner(address wallet) public view returns(bool) {
    return ownersIndices[wallet] > 0;
  }

  function ownersCount() public view returns(uint) {
    return owners.length;
  }

  function allOperationsCount() public view returns(uint) {
    return allOperations.length;
  }

  /**
  * @dev Allows to perform method by any of the owners
  */
  modifier onlyAnyOwner {
    if (checkHowManyOwners(1)) {
      bool update = (insideCallSender == address(0));
      if (update) {
        insideCallSender = msg.sender;
        insideCallCount = 1;
      }
      _;
      if (update) {
        insideCallSender = address(0);
        insideCallCount = 0;
      }
    }
  }

  /**
  * @dev Allows to perform method only after some owners call it with the same arguments
  */
  modifier onlyOwners() {
    require(howManyOwnersDecide > 0);
    require(howManyOwnersDecide <= owners.length);

    if (checkHowManyOwners(howManyOwnersDecide)) {
      bool update = (insideCallSender == address(0));
      if (update) {
        insideCallSender = msg.sender;
        insideCallCount = howManyOwnersDecide;
      }
      _;
      if (update) {
        insideCallSender = address(0);
        insideCallCount = 0;
      }
    }
  }

  constructor(address[] memory _owners) public {
    owners.push(msg.sender);
    ownersIndices[msg.sender] = 1;
    howManyOwnersDecide = 1;
    transferOwnership(_owners, 2);
  }

  /**
  * @dev onlyManyOwners modifier helper
  */
  function checkHowManyOwners(uint howMany) internal returns(bool) {
    if (insideCallSender == msg.sender) {
      require(howMany <= insideCallCount);
      return true;
    }

    uint ownerIndex = ownersIndices[msg.sender] - 1;
    require(ownerIndex < owners.length);
    bytes32 operation = keccak256(abi.encodePacked(msg.data, ownersGeneration));

    require((votesMaskByOperation[operation] & (2 ** ownerIndex)) == 0);
    votesMaskByOperation[operation] |= (2 ** ownerIndex);
    uint operationVotesCount = votesCountByOperation[operation] + 1;
    votesCountByOperation[operation] = operationVotesCount;
    if (operationVotesCount == 1) {
      allOperationsIndicies[operation] = allOperations.length;
      allOperations.push(operation);
      emit OperationCreated(operation, howMany, owners.length, msg.sender);
    }
    emit OperationUpvoted(operation, operationVotesCount, howMany, owners.length, msg.sender);

    // If enough owners confirmed the same operation
    if (votesCountByOperation[operation] == howMany) {
      deleteOperation(operation);
      emit OperationPerformed(operation, howMany, owners.length, msg.sender);
      return true;
    }

    return false;
  }

  function deleteOperation(bytes32 operation) internal {
    uint index = allOperationsIndicies[operation];
    if (index < allOperations.length - 1) { // Not last
      allOperations[index] = allOperations[allOperations.length - 1];
      allOperationsIndicies[allOperations[index]] = index;
    }
    allOperations.length--;

    delete votesMaskByOperation[operation];
    delete votesCountByOperation[operation];
    delete allOperationsIndicies[operation];
  }

  function cancelPending(bytes32 operation) public onlyAnyOwner {
    uint ownerIndex = ownersIndices[msg.sender] - 1;
    require((votesMaskByOperation[operation] & (2 ** ownerIndex)) != 0);
    votesMaskByOperation[operation] &= ~(2 ** ownerIndex);
    uint operationVotesCount = votesCountByOperation[operation] - 1;
    votesCountByOperation[operation] = operationVotesCount;
    emit OperationDownvoted(operation, operationVotesCount, owners.length, msg.sender);
    if (operationVotesCount == 0) {
      deleteOperation(operation);
      emit OperationCancelled(operation, msg.sender);
    }
  }

  function transferOwnership(address[] memory newOwners, uint256 _howManyOwnersDecide) public onlyAnyOwner {
    _transferOwnership(newOwners, _howManyOwnersDecide);
  }

  function _transferOwnership(address[] memory newOwners, uint256 newHowManyOwnersDecide) internal onlyOwners {
    require(newOwners.length > 0);
    require(newOwners.length <= 256);
    require(newHowManyOwnersDecide > 0);
    require(newHowManyOwnersDecide <= newOwners.length);

    // Reset owners reverse lookup table
    for (uint j = 0; j < owners.length; j++) {
      delete ownersIndices[owners[j]];
    }
    for (uint i = 0; i < newOwners.length; i++) {
      require(newOwners[i] != address(0));
      require(ownersIndices[newOwners[i]] == 0);
      ownersIndices[newOwners[i]] = i + 1;
    }

    emit OwnershipTransferred(owners, howManyOwnersDecide, newOwners, newHowManyOwnersDecide);
    owners = newOwners;
    howManyOwnersDecide = newHowManyOwnersDecide;
    allOperations.length = 0;
    ownersGeneration++;
  }

}
function checkHowManyOwners(uint howMany) internal returns(bool) {
  if (insideCallSender == msg.sender) {
    require(howMany <= insideCallCount);
    return true;
  }

  uint ownerIndex = ownersIndices[msg.sender] - 1;
  require(ownerIndex < owners.length);
  bytes32 operation = keccak256(abi.encodePacked(msg.data, ownersGeneration));

  require((votesMaskByOperation[operation] & (2 ** ownerIndex)) == 0);
  votesMaskByOperation[operation] |= (2 ** ownerIndex);
  uint operationVotesCount = votesCountByOperation[operation] + 1;
  votesCountByOperation[operation] = operationVotesCount;
  if (operationVotesCount == 1) {
    allOperationsIndicies[operation] = allOperations.length;
    allOperations.push(operation);
    emit OperationCreated(operation, howMany, owners.length, msg.sender);
  }
  emit OperationUpvoted(operation, operationVotesCount, howMany, owners.length, msg.sender);

  // If enough owners confirmed the same operation
  if (votesCountByOperation[operation] == howMany) {
    deleteOperation(operation);
    emit OperationPerformed(operation, howMany, owners.length, msg.sender);
    return true;
  }

  return false;
}

/**
 * @dev onlyManyOwners modifier helper
 */