contract EticaRelease is EticaToken {
  /* --------- PROD VALUES -------------  */
uint public REWARD_INTERVAL = 7 days; // periods duration 7 jours
uint public STAKING_DURATION = 28 days; // default stake duration 28 jours
uint public DEFAULT_VOTING_TIME = 21 days; // default voting duration 21 days
uint public DEFAULT_REVEALING_TIME = 7 days; // default revealing duration 7 days
    /* --------- PROD VALUES ------------- */

/* --------- TESTING VALUES -------------
uint public REWARD_INTERVAL = 1 minutes; // periods duration 7 jours
uint public STAKING_DURATION = 4 minutes; // default stake duration 28 jours
uint public DEFAULT_VOTING_TIME = 3 minutes; // default voting duration 21 days
uint public DEFAULT_REVEALING_TIME = 1 minutes; // default revealing duration 7 days
 --------- TESTING VALUES -------------*/

uint public DISEASE_CREATION_AMOUNT = 100 * 10**uint(decimals); // 100 ETI amount to pay for creating a new disease. Necessary in order to avoid spam. Will create a function that periodically increase it in order to take into account inflation
uint public PROPOSAL_DEFAULT_VOTE = 10 * 10**uint(decimals); // 10 ETI amount to vote for creating a new proposal. Necessary in order to avoid spam. Will create a function that periodically increase it in order to take into account inflation


uint public APPROVAL_THRESHOLD = 5000; // threshold for proposal to be accepted. 5000 means 50.00 %, 6000 would mean 60.00%
uint public PERIODS_PER_THRESHOLD = 5; // number of Periods before readjusting APPROVAL_THRESHOLD
uint public SEVERITY_LEVEL = 4; // level of severity of the protocol, the higher the more slash to wrong voters
uint public PROPOSERS_INCREASER = 3; // the proposers should get more slashed than regular voters to avoid spam, the higher this var the more severe the protocol will be against bad proposers
uint public PROTOCOL_RATIO_TARGET = 7250; // 7250 means the Protocol has a goal of 72.50% proposals approved and 27.5% proposals rejected
uint public LAST_PERIOD_COST_UPDATE = 0;


struct Period{
    uint id;
    uint interval;
    uint curation_sum; // used for proposals weight system
    uint editor_sum; // used for proposals weight system
    uint reward_for_curation; // total ETI issued to be used as Period reward for Curation
    uint reward_for_editor; // total ETI issued to be used as Period reward for Editor
    uint forprops; // number of accepted proposals in this period
    uint againstprops; // number of rejected proposals in this period
}

  struct Stake{
      uint amount;
      uint endTime; // Time when the stake will be claimable
  }

// -----------  PROPOSALS STRUCTS  ------------  //

// general information of Proposal:
  struct Proposal{
      uint id;
      bytes32 proposed_release_hash; // Hash of "raw_release_hash + name of Disease"
      bytes32 disease_id;
      uint period_id;
      uint chunk_id;
      address proposer; // address of the proposer
      string title; // Title of the Proposal
      string description; // Description of the Proposal
      string freefield;
      string raw_release_hash;
  }

// main data of Proposal:
  struct ProposalData{

      uint starttime; // epoch time of the proposal
      uint endtime;  // voting limite
      uint finalized_time; // when first clmpropbyhash() was called
      ProposalStatus status; // Only updates once, when the voting process is over
      ProposalStatus prestatus; // Updates During voting process
      bool istie;  // will be initialized with value 0. if prop is tie it won't slash nor reward participants
      uint nbvoters;
      uint slashingratio; // solidity does not support float type. So will emulate float type by using uint
      uint forvotes;
      uint againstvotes;
      uint lastcuration_weight; // period curation weight of proposal
      uint lasteditor_weight; // period editor weight of proposal
  }

  // -----------  PROPOSALS STRUCTS ------------  //

    // -----------  CHUNKS STRUCTS ------------  //

    struct Chunk{
    uint id;
    bytes32 diseaseid; // hash of the disease
    uint idx;
    string title;
    string desc;
  }

  // -----------  CHUNKS STRUCTS ------------  //

  // -----------  VOTES STRUCTS ----------------  //
  struct Vote{
    bytes32 proposal_hash; // proposed_release_hash of proposal
    bool approve;
    bool is_editor;
    uint amount;
    address voter; // address of the voter
    uint timestamp; // epoch time of the vote
    bool is_claimed; // keeps track of whether or not vote has been claimed to avoid double claim on same vote
  }

    struct Commit{
    uint amount;
    uint timestamp; // epoch time of the vote
  }
    // -----------  VOTES STRUCTS ----------------  //

    // -----------  DISEASES STRUCTS ----------------  //

  struct Disease{
      bytes32 disease_hash;
      string name;
  }

     // -----------  DISEASES STRUCTS ----------------  //

enum ProposalStatus { Rejected, Accepted, Pending, Singlevoter }

mapping(uint => Period) public periods;
uint public periodsCounter;
mapping(uint => uint) public PeriodsIssued; // keeps track of which periods have already issued ETI
uint public PeriodsIssuedCounter;
mapping(uint => uint) public IntervalsPeriods; // keeps track of which intervals have already a period
uint public IntervalsPeriodsCounter;

mapping(uint => Disease) public diseases; // keeps track of which intervals have already a period
uint public diseasesCounter;
mapping(bytes32 => uint) public diseasesbyIds; // get disease.index by giving its disease_hash: example: [leiojej757575ero] => [0]  where leiojej757575ero is disease_hash of a Disease
mapping(string => bytes32) private diseasesbyNames; // get disease.disease_hash by giving its name: example: ["name of a disease"] => [leiojej757575ero]  where leiojej757575ero is disease_hash of a Disease. Set visibility to private because mapping with strings as keys have issues when public visibility

mapping(bytes32 => mapping(uint => bytes32)) public diseaseproposals; // mapping of mapping of all proposals for a disease
mapping(bytes32 => uint) public diseaseProposalsCounter; // keeps track of how many proposals for each disease

// -----------  PROPOSALS MAPPINGS ------------  //
mapping(bytes32 => Proposal) public proposals;
mapping(uint => bytes32) public proposalsbyIndex; // get proposal.proposed_release_hash by giving its id (index): example: [2] => [huhihgfytoouhi]  where huhihgfytoouhi is proposed_release_hash of a Proposal
uint public proposalsCounter;

mapping(bytes32 => ProposalData) public propsdatas;
// -----------  PROPOSALS MAPPINGS ------------  //

// -----------  CHUNKS MAPPINGS ----------------  //
mapping(uint => Chunk) public chunks;
uint public chunksCounter;
mapping(bytes32 => mapping(uint => uint)) public diseasechunks; // chunks of a disease
mapping(uint => mapping(uint => bytes32)) public chunkproposals; // proposals of a chunk
mapping(bytes32 => uint) public diseaseChunksCounter; // keeps track of how many chunks for each disease
mapping(uint => uint) public chunkProposalsCounter; // keeps track of how many proposals for each chunk
// -----------  CHUNKS MAPPINGS ----------------  //

// -----------  VOTES MAPPINGS ----------------  //
mapping(bytes32 => mapping(address => Vote)) public votes;
mapping(address => mapping(bytes32 => Commit)) public commits;
// -----------  VOTES MAPPINGS ----------------  //

mapping(address => uint) public bosoms;
mapping(address => mapping(uint => Stake)) public stakes;
mapping(address => uint) public stakesCounters; // keeps track of how many stakes for each user
mapping(address => uint) public stakesAmount; // keeps track of total amount of stakes for each user

// Blocked ETI amount, user has votes with this amount in process and can't retrieve this amount before the system knows if the user has to be slahed
mapping(address => uint) public blockedeticas;

// ---------- EVENTS ----------- //
event CreatedPeriod(uint indexed period_id, uint interval);
event NewDisease(uint indexed diseaseindex, string title);
event NewProposal(bytes32 proposed_release_hash, address indexed _proposer, bytes32 indexed diseasehash, uint indexed chunkid);
event NewChunk(uint indexed chunkid, bytes32 indexed diseasehash);
event RewardClaimed(address indexed voter, uint amount, bytes32 proposal_hash);
event NewFee(address indexed voter, uint fee, bytes32 proposal_hash);
event NewSlash(address indexed voter, uint duration, bytes32 proposal_hash);
event NewCommit(address indexed _voter, bytes32 votehash);
event NewReveal(address indexed _voter, bytes32 indexed _proposal);
event NewStake(address indexed staker, uint amount);
event StakeClaimed(address indexed staker, uint stakeamount);
// ----------- EVENTS ---------- //



// -------------  PUBLISHING SYSTEM REWARD FUNCTIONS ---------------- //

function issue(uint _id) internal returns (bool success) {
  // we check whether there is at least one period
  require(periodsCounter > 0);

  // we check that the period exists
  require(_id > 0 && _id <= periodsCounter);

  // we retrieve the period
  Period storage period = periods[_id];

  // we check that the period is legit and has been retrieved
  require(period.id != 0);


//only allow one issuance for each period
uint rwd = PeriodsIssued[period.id];
if(rwd != 0x0) revert();  //prevent the same period from issuing twice

uint _periodsupply;

// Phase 2 (after 21 000 000 ETI has been reached)
if(supply >= 21000000 * 10**(decimals)){
_periodsupply = uint((supply.mul(inflationrate)).div(10**(31)));
}
// Phase 1 (before 21 000 000 ETI has been reached)
else {
  _periodsupply = periodrewardtemp;
}

// update Period Reward:
period.reward_for_curation = uint((_periodsupply.mul(PERIOD_CURATION_REWARD_RATIO)).div(10**(11)));
period.reward_for_editor = uint((_periodsupply.mul(PERIOD_EDITOR_REWARD_RATIO)).div(10**(11)));


supply = supply.add(_periodsupply);
balances[address(this)] = balances[address(this)].add(_periodsupply);
PeriodsIssued[period.id] = _periodsupply;
PeriodsIssuedCounter = PeriodsIssuedCounter.add(1);

return true;

}


// create a period
function newPeriod() internal {

  uint _interval = uint((block.timestamp).div(REWARD_INTERVAL));

  //only allow one period for each interval
  uint rwd = IntervalsPeriods[_interval];
  if(rwd != 0x0) revert();  //prevent the same interval from having 2 periods


  periodsCounter = periodsCounter.add(1);

  // store this interval period
  periods[periodsCounter] = Period(
    periodsCounter,
    _interval,
    0x0, //_curation_sum
    0x0, //_editor_sum
    0x0, //_reward_for_curation
    0x0, //_reward_for_editor
    0x0, // _forprops
    0x0 //_againstprops
  );

  // an interval cannot have 2 Periods
  IntervalsPeriods[_interval] = periodsCounter;
  IntervalsPeriodsCounter = IntervalsPeriodsCounter.add(1);

  // issue ETI for this Period Reward
  issue(periodsCounter);


  //readjust APPROVAL_THRESHOLD every PERIODS_PER_THRESHOLD periods:
  if((periodsCounter.sub(1)) % PERIODS_PER_THRESHOLD == 0 && periodsCounter > 1)
  {
    readjustThreshold();
  }

  emit CreatedPeriod(periodsCounter, _interval);
}

function readjustThreshold() internal {

uint _meanapproval = 0;
uint _totalfor = 0; // total of proposals accepetd
uint _totalagainst = 0; // total of proposals rejected


// calculate the mean approval rate (forprops / againstprops) of last PERIODS_PER_THRESHOLD Periods:
for(uint _periodidx = periodsCounter.sub(PERIODS_PER_THRESHOLD); _periodidx <= periodsCounter.sub(1);  _periodidx++){
   _totalfor = _totalfor.add(periods[_periodidx].forprops);
   _totalagainst = _totalagainst.add(periods[_periodidx].againstprops); 
}

  if(_totalfor.add(_totalagainst) == 0){
   _meanapproval = 5000;
  }
  else{
   _meanapproval = uint(_totalfor.mul(10000).div(_totalfor.add(_totalagainst)));
  }

// increase or decrease APPROVAL_THRESHOLD based on comparason between _meanapproval and PROTOCOL_RATIO_TARGET:

         // if there were not enough approvals:
         if( _meanapproval < PROTOCOL_RATIO_TARGET )
         {
           uint shortage_approvals_rate = (PROTOCOL_RATIO_TARGET.sub(_meanapproval));

           // require lower APPROVAL_THRESHOLD for next period:
           APPROVAL_THRESHOLD = uint(APPROVAL_THRESHOLD.sub(((APPROVAL_THRESHOLD.sub(4500)).mul(shortage_approvals_rate)).div(10000)));   // decrease by up to 27.50 % of (APPROVAL_THRESHOLD - 45)
         }else{
           uint excess_approvals_rate = uint((_meanapproval.sub(PROTOCOL_RATIO_TARGET)));

           // require higher APPROVAL_THRESHOLD for next period:
           APPROVAL_THRESHOLD = uint(APPROVAL_THRESHOLD.add(((10000 - APPROVAL_THRESHOLD).mul(excess_approvals_rate)).div(10000)));   // increase by up to 27.50 % of (100 - APPROVAL_THRESHOLD)
         }


         if(APPROVAL_THRESHOLD < 4500) // high discouragement to vote against proposals
         {
           APPROVAL_THRESHOLD = 4500;
         }

         if(APPROVAL_THRESHOLD > 9900) // high discouragement to vote for proposals
         {
           APPROVAL_THRESHOLD = 9900;
         }

}

// -------------  PUBLISHING SYSTEM REWARD FUNCTIONS ---------------- //


// -------------------- STAKING ----------------------- //
// eticatobosoms(): Stake etica in exchange for bosom
function eticatobosoms(address _staker, uint _amount) public returns (bool success){
  require(msg.sender == _staker);
  require(_amount > 0); // even if transfer require amount > 0 I prefer checking for more security and very few more gas
  // transfer _amount ETI from staker wallet to contract balance:
  transfer(address(this), _amount);

  // Get bosoms and add Stake
  bosomget(_staker, _amount);


  return true;

}



// ----  Get bosoms  ------  //

//bosomget(): Get bosoms and add Stake. Only contract is able to call this function:
function bosomget (address _staker, uint _amount) internal {

addStake(_staker, _amount);
bosoms[_staker] = bosoms[_staker].add(_amount);

}

// ----  Get bosoms  ------  //

// ----  add Stake ------  //

function addStake(address _staker, uint _amount) internal returns (bool success) {

    require(_amount > 0);
    stakesCounters[_staker] = stakesCounters[_staker].add(1); // notice that first stake will have the index of 1 thus not 0 !


    // increase variable that keeps track of total value of user's stakes
    stakesAmount[_staker] = stakesAmount[_staker].add(_amount);

    uint endTime = block.timestamp.add(STAKING_DURATION);

    // store this stake in _staker's stakes with the index stakesCounters[_staker]
    stakes[_staker][stakesCounters[_staker]] = Stake(
      _amount, // stake amount
      endTime // endTime
    );

    emit NewStake(_staker, _amount);

    return true;
}

function addConsolidation(address _staker, uint _amount, uint _endTime) internal returns (bool success) {

    require(_amount > 0);
    stakesCounters[_staker] = stakesCounters[_staker].add(1); // notice that first stake will have the index of 1 thus not 0 !


    // increase variable that keeps track of total value of user's stakes
    stakesAmount[_staker] = stakesAmount[_staker].add(_amount);

    // store this stake in _staker's stakes with the index stakesCounters[_staker]
    stakes[_staker][stakesCounters[_staker]] = Stake(
      _amount, // stake amount
      _endTime // endTime
    );

    emit NewStake(_staker, _amount);

    return true;
}

// ----  add Stake ------  //

// ----  split Stake ------  //

function splitStake(address _staker, uint _amount, uint _endTime) internal returns (bool success) {

    require(_amount > 0);
    stakesCounters[_staker] = stakesCounters[_staker].add(1); // notice that first stake will have the index of 1 thus not 0 !

    // store this stake in _staker's stakes with the index stakesCounters[_staker]
    stakes[_staker][stakesCounters[_staker]] = Stake(
      _amount, // stake amount
      _endTime // endTime
    );


    return true;
}

// ----  split Stake ------  //


// ----  Redeem a Stake ------  //
//stakeclmidx(): redeem a stake by its index
function stakeclmidx (uint _stakeidx) public {

  // we check that the stake exists
  require(_stakeidx > 0 && _stakeidx <= stakesCounters[msg.sender]);

  // we retrieve the stake
  Stake storage _stake = stakes[msg.sender][_stakeidx];

  // The stake must be over
  require(block.timestamp > _stake.endTime);

  // the amount to be unstaked must be less or equal to the amount of ETI currently marked as blocked in blockedeticas as they need to go through the clmpropbyhash before being unstaked !
  require(_stake.amount <= stakesAmount[msg.sender].sub(blockedeticas[msg.sender]));

  // transfer back ETI from contract to staker:
  balances[address(this)] = balances[address(this)].sub(_stake.amount);

  balances[msg.sender] = balances[msg.sender].add(_stake.amount);

  emit Transfer(address(this), msg.sender, _stake.amount);
  emit StakeClaimed(msg.sender, _stake.amount);

  // deletes the stake
  _deletestake(msg.sender, _stakeidx);

}

// ----  Redeem a Stake ------  //

// ----  Remove a Stake ------  //

function _deletestake(address _staker,uint _index) internal {
  // we check that the stake exists
  require(_index > 0 && _index <= stakesCounters[_staker]);

  // decrease variable that keeps track of total value of user's stakes
  stakesAmount[_staker] = stakesAmount[_staker].sub(stakes[_staker][_index].amount);

  // replace value of stake to be deleted by value of last stake
  stakes[_staker][_index] = stakes[_staker][stakesCounters[_staker]];

  // remove last stake
  stakes[_staker][stakesCounters[_staker]] = Stake(
    0x0, // amount
    0x0 // endTime
    );

  // updates stakesCounter of _staker
  stakesCounters[_staker] = stakesCounters[_staker].sub(1);

}

// ----  Remove a Stake ------  //


// ----- Stakes consolidation  ----- //

// slashing function needs to loop through stakes. Can create issues for claiming votes:
// The function stakescsldt() has been created to consolidate (gather) stakes when user has too much stakes
function stakescsldt(uint _endTime, uint _min_limit, uint _maxidx) public {

// security to avoid blocking ETI by front end apps that could call function with too high _endTime:
require(_endTime < block.timestamp.add(730 days)); // _endTime cannot be more than two years ahead  

// _maxidx must be less or equal to nb of stakes and we set a limit for loop of 50:
require(_maxidx <= 50 && _maxidx <= stakesCounters[msg.sender]);

uint newAmount = 0;

uint _nbdeletes = 0;

uint _currentidx = 1;

for(uint _stakeidx = 1; _stakeidx <= _maxidx;  _stakeidx++) {
    // only consolidates if account nb of stakes >= 2 :
    if(stakesCounters[msg.sender] >= 2){

    if(_stakeidx <= stakesCounters[msg.sender]){
       _currentidx = _stakeidx;
    } 
    else {
      // if _stakeidx > stakesCounters[msg.sender] it means the _deletestake() function has pushed the next stakes at the begining:
      _currentidx = _stakeidx.sub(_nbdeletes); //Notice: initial stakesCounters[msg.sender] = stakesCounters[msg.sender] + _nbdeletes. 
      //So "_stackidx <= _maxidx <= initial stakesCounters[msg.sender]" ===> "_stakidx <= stakesCounters[msg.sender] + _nbdeletes" ===> "_stackidx - _nbdeletes <= stakesCounters[msg.sender]"
      assert(_currentidx >= 1); // makes sure _currentidx is within existing stakes range
    }
      
      //if stake should end sooner than _endTime it can be consolidated into a stake that end latter:
      // Plus we check the stake.endTime is above the minimum limit the user is willing to consolidate. For instance user doesn't want to consolidate a stake that is ending tomorrow
      if(stakes[msg.sender][_currentidx].endTime <= _endTime && stakes[msg.sender][_currentidx].endTime >= _min_limit) {

        newAmount = newAmount.add(stakes[msg.sender][_currentidx].amount);

        _deletestake(msg.sender, _currentidx);    

        _nbdeletes = _nbdeletes.add(1);

      }  

    }
}

if (newAmount > 0){
// creates the new Stake
addConsolidation(msg.sender, newAmount, _endTime);
}

}

// ----- Stakes consolidation  ----- //

// ----- Stakes de-consolidation  ----- //

// this function is necessary because if user has a stake with huge amount and has blocked few ETI then he can't claim the Stake because
// stake.amount > StakesAmount - blockedeticas
function stakesnap(uint _stakeidx, uint _snapamount) public {

  require(_snapamount > 0);
  
  // we check that the stake exists
  require(_stakeidx > 0 && _stakeidx <= stakesCounters[msg.sender]);

  // we retrieve the stake
  Stake storage _stake = stakes[msg.sender][_stakeidx];


  // the stake.amount must be higher than _snapamount:
  require(_stake.amount > _snapamount);

  // calculate the amount of new stake:
  uint _restAmount = _stake.amount.sub(_snapamount);
  
  // updates the stake amount:
  _stake.amount = _snapamount;


  // ----- creates a new stake with the rest -------- //
  stakesCounters[msg.sender] = stakesCounters[msg.sender].add(1);

  // store this stake in _staker's stakes with the index stakesCounters[_staker]
  stakes[msg.sender][stakesCounters[msg.sender]] = Stake(
      _restAmount, // stake amount
      _stake.endTime // endTime
    );
  // ------ creates a new stake with the rest ------- //  

assert(_restAmount > 0);

}

// ----- Stakes de-consolidation  ----- //


function stakescount(address _staker) public view returns (uint slength){
  return stakesCounters[_staker];
}

// ----------------- STAKING ------------------ //


// -------------  PUBLISHING SYSTEM CORE FUNCTIONS ---------------- //
function createdisease(string memory _name) public {


  // --- REQUIRE PAYMENT FOR ADDING A DISEASE TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //

  // make sure the user has enough ETI to create a disease
  require(balances[msg.sender] >= DISEASE_CREATION_AMOUNT);
  // transfer DISEASE_CREATION_AMOUNT ETI from user wallet to contract wallet:
  transfer(address(this), DISEASE_CREATION_AMOUNT);

  UNRECOVERABLE_ETI = UNRECOVERABLE_ETI.add(DISEASE_CREATION_AMOUNT);

  // --- REQUIRE PAYMENT FOR ADDING A DISEASE TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //


  bytes32 _diseasehash = keccak256(abi.encode(_name));

  diseasesCounter = diseasesCounter.add(1); // notice that first disease will have the index of 1 thus not 0 !

  //check: if the disease is new we continue, otherwise we exit
   if(diseasesbyIds[_diseasehash] != 0x0) revert();  //prevent the same disease from being created twice. The software manages diseases uniqueness based on their unique english name. Note that even the first disease will not have index of 0 thus should pass this check
   require(diseasesbyNames[_name] == 0); // make sure it is not overwriting another disease thanks to unexpected string tricks from user

   // store the Disease
   diseases[diseasesCounter] = Disease(
     _diseasehash,
     _name
   );

   // Updates diseasesbyIds and diseasesbyNames mappings:
   diseasesbyIds[_diseasehash] = diseasesCounter;
   diseasesbyNames[_name] = _diseasehash;

   emit NewDisease(diseasesCounter, _name);

}



function propose(bytes32 _diseasehash, string memory _title, string memory _description, string memory raw_release_hash, string memory _freefield, uint _chunkid) public {

    //check if the disease exits
     require(diseasesbyIds[_diseasehash] > 0 && diseasesbyIds[_diseasehash] <= diseasesCounter);
     if(diseases[diseasesbyIds[_diseasehash]].disease_hash != _diseasehash) revert(); // second check not necessary but I decided to add it as the gas cost value for security is worth it

    require(_chunkid <= chunksCounter);

     bytes32 _proposed_release_hash = keccak256(abi.encode(raw_release_hash, _diseasehash));
     diseaseProposalsCounter[_diseasehash] = diseaseProposalsCounter[_diseasehash].add(1);
     diseaseproposals[_diseasehash][diseaseProposalsCounter[_diseasehash]] = _proposed_release_hash;

     proposalsCounter = proposalsCounter.add(1); // notice that first proposal will have the index of 1 thus not 0 !
     proposalsbyIndex[proposalsCounter] = _proposed_release_hash;

     // Check that proposal does not already exist
     // only allow one proposal for each {raw_release_hash,  _diseasehash} combinasion
      bytes32 existing_proposal = proposals[_proposed_release_hash].proposed_release_hash;
      if(existing_proposal != 0x0 || proposals[_proposed_release_hash].id != 0) revert();  //prevent the same raw_release_hash from being submited twice on same proposal. Double check for better security and slightly higher gas cost even though one would be enough !

     uint _current_interval = uint((block.timestamp).div(REWARD_INTERVAL));

      // Create new Period if this current interval did not have its Period created yet
      if(IntervalsPeriods[_current_interval] == 0x0){
        newPeriod();
      }

     Proposal storage proposal = proposals[_proposed_release_hash];

       proposal.id = proposalsCounter;
       proposal.disease_id = _diseasehash; // _diseasehash has already been checked to equal diseases[diseasesbyIds[_diseasehash]].disease_hash
       proposal.period_id = IntervalsPeriods[_current_interval];
       proposal.proposed_release_hash = _proposed_release_hash; // Hash of "raw_release_hash + name of Disease",
       proposal.proposer = msg.sender;
       proposal.title = _title;
       proposal.description = _description;
       proposal.raw_release_hash = raw_release_hash;
       proposal.freefield = _freefield;


       //  Proposal Data:
       ProposalData storage proposaldata = propsdatas[_proposed_release_hash];
       proposaldata.status = ProposalStatus.Pending;
       proposaldata.istie = true;
       proposaldata.prestatus = ProposalStatus.Pending;
       proposaldata.nbvoters = 0;
       proposaldata.slashingratio = 0;
       proposaldata.forvotes = 0;
       proposaldata.againstvotes = 0;
       proposaldata.lastcuration_weight = 0;
       proposaldata.lasteditor_weight = 0;
       proposaldata.starttime = block.timestamp;
       proposaldata.endtime = block.timestamp.add(DEFAULT_VOTING_TIME);


// --- REQUIRE DEFAULT VOTE TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //

    require(bosoms[msg.sender] >= PROPOSAL_DEFAULT_VOTE); // this check is not mandatory as handled by safemath sub function: (bosoms[msg.sender].sub(PROPOSAL_DEFAULT_VOTE))

    // Consume bosom:
    bosoms[msg.sender] = bosoms[msg.sender].sub(PROPOSAL_DEFAULT_VOTE);


    // Block Eticas in eticablkdtbl to prevent user from unstaking before eventual slash
    blockedeticas[msg.sender] = blockedeticas[msg.sender].add(PROPOSAL_DEFAULT_VOTE);


    // store vote:
    Vote storage vote = votes[proposal.proposed_release_hash][msg.sender];
    vote.proposal_hash = proposal.proposed_release_hash;
    vote.approve = true;
    vote.is_editor = true;
    vote.amount = PROPOSAL_DEFAULT_VOTE;
    vote.voter = msg.sender;
    vote.timestamp = block.timestamp;



      // UPDATE PROPOSAL:
      proposaldata.prestatus = ProposalStatus.Singlevoter;

      // if chunk exists and belongs to disease updates proposal.chunk_id:
      uint existing_chunk = chunks[_chunkid].id;
      if(existing_chunk != 0x0 && chunks[_chunkid].diseaseid == _diseasehash) {
        proposal.chunk_id = _chunkid;
        // updates chunk proposals infos:
        chunkProposalsCounter[_chunkid] = chunkProposalsCounter[_chunkid].add(1);
        chunkproposals[_chunkid][chunkProposalsCounter[_chunkid]] = proposal.proposed_release_hash;
      }

  // --- REQUIRE DEFAULT VOTE TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //

  RANDOMHASH = keccak256(abi.encode(RANDOMHASH, _proposed_release_hash)); // updates RANDOMHASH

    emit NewProposal(_proposed_release_hash, msg.sender, proposal.disease_id, _chunkid);

}


 function updatecost() public {

// only start to increase PROPOSAL AND DISEASE COSTS once we are in phase2
require(supply >= 21000000 * 10**(decimals));
// update disease and proposal cost each 52 periods to take into account inflation:
require(periodsCounter % 52 == 0);
uint _new_disease_cost = supply.mul(47619046).div(10**13); // disease cost is 0.00047619046% of supply
uint _new_proposal_vote = supply.mul(47619046).div(10**14); // default vote amount is 0.000047619046% of supply

PROPOSAL_DEFAULT_VOTE = _new_proposal_vote;
DISEASE_CREATION_AMOUNT = _new_disease_cost;

assert(LAST_PERIOD_COST_UPDATE < periodsCounter);
LAST_PERIOD_COST_UPDATE = periodsCounter;

 }



 function commitvote(uint _amount, bytes32 _votehash) public {

require(_amount > 10);

 // Consume bosom:
 require(bosoms[msg.sender] >= _amount); // this check is not mandatory as handled by safemath sub function
 bosoms[msg.sender] = bosoms[msg.sender].sub(_amount);

 // Block Eticas in eticablkdtbl to prevent user from unstaking before eventual slash
 blockedeticas[msg.sender] = blockedeticas[msg.sender].add(_amount);

 // store _votehash in commits with _amount and current block.timestamp value:
 commits[msg.sender][_votehash].amount = commits[msg.sender][_votehash].amount.add(_amount);
 commits[msg.sender][_votehash].timestamp = block.timestamp;

 RANDOMHASH = keccak256(abi.encode(RANDOMHASH, _votehash)); // updates RANDOMHASH

emit NewCommit(msg.sender, _votehash);

 }


 function revealvote(bytes32 _proposed_release_hash, bool _approved, string memory _vary) public {
 

// --- check commit --- //
bytes32 _votehash;
_votehash = keccak256(abi.encode(_proposed_release_hash, _approved, msg.sender, _vary));

require(commits[msg.sender][_votehash].amount > 0);
// --- check commit done --- //

//check if the proposal exists and that we get the right proposal:
Proposal storage proposal = proposals[_proposed_release_hash];
require(proposal.id > 0 && proposal.proposed_release_hash == _proposed_release_hash);


ProposalData storage proposaldata = propsdatas[_proposed_release_hash];

 // Verify commit was done within voting time:
 require( commits[msg.sender][_votehash].timestamp <= proposaldata.endtime);

 // Verify we are within revealing time:
 require( block.timestamp > proposaldata.endtime && block.timestamp <= proposaldata.endtime.add(DEFAULT_REVEALING_TIME));

 require(proposaldata.prestatus != ProposalStatus.Pending); // can vote for proposal only if default vote has changed prestatus of Proposal. Thus can vote only if default vote occured as supposed to

uint _old_proposal_curationweight = proposaldata.lastcuration_weight;
uint _old_proposal_editorweight = proposaldata.lasteditor_weight;


// get Period of Proposal:
Period storage period = periods[proposal.period_id];


// Check that vote does not already exist
// only allow one vote for each {raw_release_hash, voter} combinasion
bytes32 existing_vote = votes[proposal.proposed_release_hash][msg.sender].proposal_hash;
if(existing_vote != 0x0 || votes[proposal.proposed_release_hash][msg.sender].amount != 0) revert();  //prevent the same user from voting twice for same raw_release_hash. Double condition check for better security and slightly higher gas cost even though one would be enough !


 // store vote:
 Vote storage vote = votes[proposal.proposed_release_hash][msg.sender];
 vote.proposal_hash = proposal.proposed_release_hash;
 vote.approve = _approved;
 vote.is_editor = false;
 vote.amount = commits[msg.sender][_votehash].amount;
 vote.voter = msg.sender;
 vote.timestamp = block.timestamp;

 proposaldata.nbvoters = proposaldata.nbvoters.add(1);

     // PROPOSAL VAR UPDATE
     if(_approved){
      proposaldata.forvotes = proposaldata.forvotes.add(commits[msg.sender][_votehash].amount);
     }
     else {
       proposaldata.againstvotes = proposaldata.againstvotes.add(commits[msg.sender][_votehash].amount);
     }


     // Determine slashing conditions
     bool _isapproved = false;
     bool _istie = false;
     uint totalVotes = proposaldata.forvotes.add(proposaldata.againstvotes);
     uint _forvotes_numerator = proposaldata.forvotes.mul(10000); // (newproposal_forvotes / totalVotes) will give a number between 0 and 1. Multiply by 10000 to store it as uint
     uint _ratio_slashing = 0;

     if ((_forvotes_numerator.div(totalVotes)) > APPROVAL_THRESHOLD){
    _isapproved = true;
    }
    if ((_forvotes_numerator.div(totalVotes)) == APPROVAL_THRESHOLD){
        _istie = true;
    }

    proposaldata.istie = _istie;

    if (_isapproved){
    _ratio_slashing = uint(((10000 - APPROVAL_THRESHOLD).mul(totalVotes)).div(10000));
    _ratio_slashing = uint((proposaldata.againstvotes.mul(10000)).div(_ratio_slashing));  
    proposaldata.slashingratio = uint(10000 - _ratio_slashing);
    }
    else{
    _ratio_slashing = uint((totalVotes.mul(APPROVAL_THRESHOLD)).div(10000));
    _ratio_slashing = uint((proposaldata.forvotes.mul(10000)).div(_ratio_slashing));
    proposaldata.slashingratio = uint(10000 - _ratio_slashing);
    }

    // Make sure the slashing reward ratio is within expected range:
     require(proposaldata.slashingratio >=0 && proposaldata.slashingratio <= 10000);

        // updates period forvotes and againstvotes system
        ProposalStatus _newstatus = ProposalStatus.Rejected;
        if(_isapproved){
         _newstatus = ProposalStatus.Accepted;
        }

        if(proposaldata.prestatus == ProposalStatus.Singlevoter){

          if(_isapproved){
            period.forprops = period.forprops.add(1);
          }
          else {
            period.againstprops = period.againstprops.add(1);
          }
        }
        // in this case the proposal becomes rejected after being accepted or becomes accepted after being rejected:
        else if(_newstatus != proposaldata.prestatus){

         if(_newstatus == ProposalStatus.Accepted){
          period.againstprops = period.againstprops.sub(1);
          period.forprops = period.forprops.add(1);
         }
         // in this case proposal is necessarily Rejected:
         else {
          period.forprops = period.forprops.sub(1);
          period.againstprops = period.againstprops.add(1);
         }

        }
        // updates period forvotes and againstvotes system done

         // Proposal and Period new weight
         if (_istie) {
         proposaldata.prestatus =  ProposalStatus.Rejected;
         proposaldata.lastcuration_weight = 0;
         proposaldata.lasteditor_weight = 0;
         // Proposal tied, remove proposal curation and editor sum
         period.curation_sum = period.curation_sum.sub(_old_proposal_curationweight);
         period.editor_sum = period.editor_sum.sub(_old_proposal_editorweight);
         }
         else {
             // Proposal approved, strengthen curation sum
         if (_isapproved){
             proposaldata.prestatus =  ProposalStatus.Accepted;
             proposaldata.lastcuration_weight = proposaldata.forvotes;
             proposaldata.lasteditor_weight = proposaldata.forvotes;
             // Proposal approved, replace proposal curation and editor sum with forvotes
             period.curation_sum = period.curation_sum.sub(_old_proposal_curationweight).add(proposaldata.lastcuration_weight);
             period.editor_sum = period.editor_sum.sub(_old_proposal_editorweight).add(proposaldata.lasteditor_weight);
         }
         else{
             proposaldata.prestatus =  ProposalStatus.Rejected;
             proposaldata.lastcuration_weight = proposaldata.againstvotes;
             proposaldata.lasteditor_weight = 0;
             // Proposal rejected, replace proposal curation sum with againstvotes and remove proposal editor sum
             period.curation_sum = period.curation_sum.sub(_old_proposal_curationweight).add(proposaldata.lastcuration_weight);
             period.editor_sum = period.editor_sum.sub(_old_proposal_editorweight);
         }
         }
         
        // resets commit to save space: 
        _removecommit(_votehash);
        emit NewReveal(msg.sender, proposal.proposed_release_hash);

  }

  function _removecommit(bytes32 _votehash) internal {
        commits[msg.sender][_votehash].amount = 0;
        commits[msg.sender][_votehash].timestamp = 0;
  }


  function clmpropbyhash(bytes32 _proposed_release_hash) public {

   //check if the proposal exists and that we get the right proposal:
   Proposal storage proposal = proposals[_proposed_release_hash];
   require(proposal.id > 0 && proposal.proposed_release_hash == _proposed_release_hash);


   ProposalData storage proposaldata = propsdatas[_proposed_release_hash];
   // Verify voting and revealing period is over
   require( block.timestamp > proposaldata.endtime.add(DEFAULT_REVEALING_TIME));

   
    // we check that the vote exists
    Vote storage vote = votes[proposal.proposed_release_hash][msg.sender];
    require(vote.proposal_hash == _proposed_release_hash);
    
    // make impossible to claim same vote twice
    require(!vote.is_claimed);
    vote.is_claimed = true;



  
    // De-Block Eticas from eticablkdtbl to enable user to unstake these Eticas
    blockedeticas[msg.sender] = blockedeticas[msg.sender].sub(vote.amount);


    // get Period of Proposal:
    Period storage period = periods[proposal.period_id];

   uint _current_interval = uint((block.timestamp).div(REWARD_INTERVAL));

   // Check if Period is ready for claims or if it needs to wait more
   uint _min_intervals = uint(((DEFAULT_VOTING_TIME.add(DEFAULT_REVEALING_TIME)).div(REWARD_INTERVAL)).add(1)); // Minimum intervals before claimable
   require(_current_interval >= period.interval.add(_min_intervals)); // Period not ready for claims yet. Need to wait more !

  // if status equals pending this is the first claim for this proposal
  if (proposaldata.status == ProposalStatus.Pending) {

  // SET proposal new status
  if (proposaldata.prestatus == ProposalStatus.Accepted) {
            proposaldata.status = ProposalStatus.Accepted;
  }
  else {
    proposaldata.status = ProposalStatus.Rejected;
  }

  proposaldata.finalized_time = block.timestamp;

  // NEW STATUS AFTER FIRST CLAIM DONE

  }


  // only slash and reward if prop is not tie:
  if (!proposaldata.istie) {
   
   // convert boolean to enum format for making comparasion with proposaldata.status possible:
   ProposalStatus voterChoice = ProposalStatus.Rejected;
   if(vote.approve){
     voterChoice = ProposalStatus.Accepted;
   }

   if(voterChoice != proposaldata.status) {
     // slash loosers: voter has voted wrongly and needs to be slashed
     uint _slashRemaining = vote.amount;
     uint _extraTimeInt = uint(STAKING_DURATION.mul(SEVERITY_LEVEL).mul(proposaldata.slashingratio).div(10000));

     if(vote.is_editor){
     _extraTimeInt = uint(_extraTimeInt.mul(PROPOSERS_INCREASER));
     }


// REQUIRE FEE if slashingratio is superior to 90.00%:
if(proposaldata.slashingratio > 9000){
    // 33% fee if voter is not proposer or 100% fee if voter is proposer
    uint _feeRemaining = uint(vote.amount.mul(33).div(100));
      if(vote.is_editor){
        _feeRemaining = vote.amount;
      }
    emit NewFee(msg.sender, _feeRemaining, vote.proposal_hash);  
    UNRECOVERABLE_ETI = UNRECOVERABLE_ETI.add(_feeRemaining);  
     // update _slashRemaining 
    _slashRemaining = vote.amount.sub(_feeRemaining);

         for(uint _stakeidxa = 1; _stakeidxa <= stakesCounters[msg.sender];  _stakeidxa++) {
      //if stake is big enough and can take into account the whole fee:
      if(stakes[msg.sender][_stakeidxa].amount > _feeRemaining) {
 
        stakes[msg.sender][_stakeidxa].amount = stakes[msg.sender][_stakeidxa].amount.sub(_feeRemaining);
        stakesAmount[msg.sender] = stakesAmount[msg.sender].sub(_feeRemaining);
        _feeRemaining = 0;
         break;
      }
      else {
        // The fee amount is more than or equal to a full stake, so the stake needs to be deleted:
          _feeRemaining = _feeRemaining.sub(stakes[msg.sender][_stakeidxa].amount);
          _deletestake(msg.sender, _stakeidxa);
          if(_feeRemaining == 0){
           break;
          }
      }
    }
}



// SLASH only if slash remaining > 0
if(_slashRemaining > 0){
  emit NewSlash(msg.sender, _slashRemaining, vote.proposal_hash);
         for(uint _stakeidx = 1; _stakeidx <= stakesCounters[msg.sender];  _stakeidx++) {
      //if stake is too small and will only be able to take into account a part of the slash:
      if(stakes[msg.sender][_stakeidx].amount <= _slashRemaining) {
 
        stakes[msg.sender][_stakeidx].endTime = stakes[msg.sender][_stakeidx].endTime.add(_extraTimeInt);
        _slashRemaining = _slashRemaining.sub(stakes[msg.sender][_stakeidx].amount);
        
       if(_slashRemaining == 0){
         break;
       }
      }
      else {
        // The slash amount does not fill a full stake, so the stake needs to be split
        uint newAmount = stakes[msg.sender][_stakeidx].amount.sub(_slashRemaining);
        uint oldCompletionTime = stakes[msg.sender][_stakeidx].endTime;

        // slash amount split in _slashRemaining and newAmount
        stakes[msg.sender][_stakeidx].amount = _slashRemaining; // only slash the part of the stake that amounts to _slashRemaining
        stakes[msg.sender][_stakeidx].endTime = stakes[msg.sender][_stakeidx].endTime.add(_extraTimeInt); // slash the stake

        if(newAmount > 0){
          // create a new stake with the rest of what remained from original stake that was split in 2
          splitStake(msg.sender, newAmount, oldCompletionTime);
        }

        break;
      }
    }
}
    // the slash is over
   }
   else {

   uint _reward_amount = 0;

   // check beforte diving by 0
   require(period.curation_sum > 0); // period curation sum pb !
   // get curation reward only if voter is not the proposer:
   if (!vote.is_editor){
   _reward_amount = _reward_amount.add((vote.amount.mul(period.reward_for_curation)).div(period.curation_sum));
   }

       // if voter is editor and proposal accepted:
    if (vote.is_editor && proposaldata.status == ProposalStatus.Accepted){
          // check before dividing by 0
          require( period.editor_sum > 0); // Period editor sum pb !
          _reward_amount = _reward_amount.add((proposaldata.lasteditor_weight.mul(period.reward_for_editor)).div(period.editor_sum));
    }

    require(_reward_amount <= period.reward_for_curation.add(period.reward_for_editor)); // "System logic error. Too much ETICA calculated for reward."

    // SEND ETICA AS REWARD
    balances[address(this)] = balances[address(this)].sub(_reward_amount);
    balances[msg.sender] = balances[msg.sender].add(_reward_amount);

    emit Transfer(address(this), msg.sender, _reward_amount);
    emit RewardClaimed(msg.sender, _reward_amount, _proposed_release_hash);
   }

  }   // end bracket if (proposaldata.istie not true)
  
  }


    function createchunk(bytes32 _diseasehash, string memory _title, string memory _description) public {

  //check if the disease exits
  require(diseasesbyIds[_diseasehash] > 0 && diseasesbyIds[_diseasehash] <= diseasesCounter);
  if(diseases[diseasesbyIds[_diseasehash]].disease_hash != _diseasehash) revert(); // second check not necessary but I decided to add it as the gas cost value for security is worth it

  // --- REQUIRE PAYMENT FOR ADDING A CHUNK TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //
  uint _cost = DISEASE_CREATION_AMOUNT.div(20);
  // make sure the user has enough ETI to create a chunk
  require(balances[msg.sender] >= _cost);
  // transfer DISEASE_CREATION_AMOUNT / 20  ETI from user wallet to contract wallet:
  transfer(address(this), _cost);

  // --- REQUIRE PAYMENT FOR ADDING A CHUNK TO CREATE A BARRIER TO ENTRY AND AVOID SPAM --- //

  chunksCounter = chunksCounter.add(1); // get general id of Chunk

  // updates disease's chunks infos:
  diseaseChunksCounter[_diseasehash] = diseaseChunksCounter[_diseasehash].add(1); // Increase chunks index of Disease
  diseasechunks[_diseasehash][diseaseChunksCounter[_diseasehash]] = chunksCounter;
  

  // store the Chunk
   chunks[chunksCounter] = Chunk(
     chunksCounter, // general id of the chunk
     _diseasehash, // disease of the chunk
     diseaseChunksCounter[_diseasehash], // Index of chunk within disease
     _title,
     _description
   );

  UNRECOVERABLE_ETI = UNRECOVERABLE_ETI.add(_cost);
  emit NewChunk(chunksCounter, _diseasehash);

  }


// -------------  PUBLISHING SYSTEM CORE FUNCTIONS ---------------- //



// -------------  GETTER FUNCTIONS ---------------- //
// get bosoms balance of user:
function bosomsOf(address tokenOwner) public view returns (uint _bosoms){
     return bosoms[tokenOwner];
 }

 function getdiseasehashbyName(string memory _name) public view returns (bytes32 _diseasehash){
     return diseasesbyNames[_name];
 }
// -------------  GETTER FUNCTIONS ---------------- //

}
function splitStake(address _staker, uint _amount, uint _endTime) internal returns (bool success) {

    require(_amount > 0);
    stakesCounters[_staker] = stakesCounters[_staker].add(1); // notice that first stake will have the index of 1 thus not 0 !

    // store this stake in _staker's stakes with the index stakesCounters[_staker]
    stakes[_staker][stakesCounters[_staker]] = Stake(
      _amount, // stake amount
      _endTime // endTime
    );


    return true;
}
// ----  add Stake ------  //
// ----  split Stake ------  //