// SPDX-License-Identifier: Apache-2.0
// Authors: six and Silur
pragma solidity ^0.8.16;

contract CCTF9 {
  address public admin;
  uint256 public volStart;
  uint256 public volMaxPoints;
  uint256 public powDiff;
  bool public started;

  enum PlayerStatus {
    Unverified,
    Verified,
    Banned
  }

  struct Player {
    PlayerStatus status;
    uint256 points;
  }

  modifier onlyAdmin {
    require(msg.sender == admin, "Not admin");
    _;
  }

  modifier onlyActive {
    require(started == true, "CCTF not started.");
    _;
  }

  struct Flag {
    address signer;
    bool onlyFirstSolver;
    uint256 points;
    string skill_name;
  }

  mapping (address => Player) public players;
  mapping (uint256 => Flag) public flags;

  event CCTFStarted(uint256 timestamp);
  event FlagAdded(uint256 indexed flagId, address flagSigner);
  event FlagRemoved(uint256 indexed flagId);
  event FlagSolved(uint256 indexed flagId, address indexed solver);

  constructor(uint256 _volMaxPoints, uint256 _powDiff) {
    admin = msg.sender;
    volMaxPoints = _volMaxPoints;
    powDiff = _powDiff;
    started = false;
  }
  
  function setAdmin(address _admin) external onlyAdmin {
    require(_admin != address(0));
    admin = _admin;
  }

  function setCCTFStatus(bool _started) external onlyAdmin {
    started = _started;
  }

  function setFlag(uint256 _flagId, address _flagSigner, bool _onlyFirstSolver, uint256 _points, string memory _skill) external onlyAdmin{
    flags[_flagId] = Flag(_flagSigner, _onlyFirstSolver, _points, _skill);
    emit FlagAdded(_flagId, _flagSigner);
  }

  function setPowDiff(uint256 _powDiff) external onlyAdmin {
    powDiff = _powDiff;
  }


  function register(string memory _RTFM) external {
    require(players[msg.sender].status == PlayerStatus.Unverified, 'Already registered or banned');
    //uint256 pow = uint256(keccak256(abi.encodePacked("CCTF", msg.sender,"registration", nonce)));
    //require(pow < powDiff, "invalid pow");
    require(keccak256(abi.encodePacked('I_read_it')) == keccak256(abi.encodePacked(_RTFM)));  // PoW can be used for harder challenges, this is Entry!
    players[msg.sender].status = PlayerStatus.Verified;
  }

  function setPlayerStatus(address player, PlayerStatus status) external onlyAdmin {
    players[player].status = status;
  }
  
  
////////// Submit flags
    mapping(bytes32 => bool) usedNs;                       // Against replay attack (we only check message signer)
    mapping (address => mapping (uint256 => bool)) Solves;     // address -> challenge ID -> solved/not
    uint256 public submission_success_count = 0;               // For statistics

    function SubmitFlag(bytes32 _message, bytes memory signature, uint256 _submitFor) external onlyActive {
        require(players[msg.sender].status == PlayerStatus.Verified, "You are not even playing");
        require(bytes32(_message).length <= 256, "Too long message.");
        require(!usedNs[_message]);
        usedNs[_message] = true;
        require(recoverSigner(_message, signature) == flags[_submitFor].signer, "Not signed with the correct key.");
        require(Solves[msg.sender][_submitFor] == false);
        
        Solves[msg.sender][_submitFor] = true;
        players[msg.sender].points += flags[_submitFor].points;
        players[msg.sender].points = players[msg.sender].points < volMaxPoints ? players[msg.sender].points : volMaxPoints;
        
        if (flags[_submitFor].onlyFirstSolver) {
            flags[_submitFor].points = 0;
        }
        
        submission_success_count = submission_success_count + 1;
        emit FlagSolved(_submitFor, msg.sender);
    }

    function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
        return ecrecover(_ethSignedMessageHash, v, r, s);
    }

    function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v){
        require(sig.length == 65, "Invalid signature length");
        assembly {
            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))
        }
    }

////////// Check status, scores, etc
  function getPlayerStatus(address _player) external view returns (PlayerStatus) {
    return players[_player].status;
  }

  function getPlayerPoints(address _player) external view returns (uint256) {
    return players[_player].points < volMaxPoints ? players[_player].points : volMaxPoints;
  } 

  function getSuccessfulSubmissionCount() external view returns (uint256){
      return submission_success_count;
  }
}
