contract PreSaleDisciple is PreSaleGuardian {
    // ----------------------------------------------------------------------------
    // Events
    // ----------------------------------------------------------------------------
    event DiscipleSaleCreate(uint indexed saleId, uint indexed discipleId, uint indexed price, uint occupation, uint level);
    event BuyDisciple(uint indexed saleId, uint discipleId, address indexed buyer, uint indexed currentPrice);
    event DiscipleOfferSubmit(uint indexed saleId, uint discipleId, address indexed bidder, uint indexed price);
    event DiscipleOfferAccept(uint indexed saleId, uint discipleId, address indexed newOwner, uint indexed newPrice);
    event SetDiscipleSale(uint indexed saleId, uint indexed price, uint occupation, uint level);
    
    event DiscipleAuctionCreate(uint indexed auctionId, uint indexed discipleId, uint indexed startPrice, uint occupation, uint level);
    event DiscipleAuctionBid(uint indexed auctionId, address indexed bidder, uint indexed offer);
    
    event VendingDisciple(uint indexed vendingId, address indexed buyer);
    event DiscipleVendOffer(uint indexed vendingId, address indexed bidder, uint indexed offer);
    event DiscipleVendAccept(uint indexed vendingId, address indexed newOwner, uint indexed newPrice);
    event SetDiscipleVend(uint indexed priceId, uint indexed price);
    
    // ----------------------------------------------------------------------------
    // Mappings
    // ----------------------------------------------------------------------------
    mapping (uint => address) public DiscipleSaleToBuyer;
    mapping (uint => bool) DiscipleIdToIfCreated;
    
    mapping (uint => uint) public DiscipleVendToOffer;
    mapping (uint => address) public DiscipleVendToBidder;
    mapping (uint => uint) public DiscipleVendToTime;
    
    // ----------------------------------------------------------------------------
    // Variables
    // ----------------------------------------------------------------------------
    struct DiscipleSale {
        uint discipleId;
        uint occupation;
        uint level;
        uint price;
        bool ifSold;
        address bidder;
        uint offerPrice;
        uint timestamp;
    }
    
    DiscipleSale[] discipleSales;

    uint[5] DiscipleVending = [
        0.8 ether,
        0.65 ether,
        0.45 ether,
        0.35 ether,
        0.2 ether
    ];
    
    // ----------------------------------------------------------------------------
    // Modifier
    // ----------------------------------------------------------------------------
    
    // ----------------------------------------------------------------------------
    // Internal Function
    // ----------------------------------------------------------------------------
    function _generateDiscipleSale(uint _discipleId, uint _occupation, uint _level, uint _price) internal returns (uint) {
        require(DiscipleIdToIfCreated[_discipleId] == false);
        DiscipleIdToIfCreated[_discipleId] = true;
        DiscipleSale memory _DiscipleSale = DiscipleSale({
            discipleId: _discipleId,
            occupation: _occupation,
            level: _level,
            price: _price,
            ifSold: false,
            bidder: address(0),
            offerPrice: 0,
            timestamp: 0
        });
        uint discipleSaleId = discipleSales.push(_DiscipleSale) - 1;
        emit DiscipleSaleCreate(discipleSaleId, _discipleId, _price, _occupation, _level);
        
        return discipleSaleId;
    }
    
    function _discipleVendPrice(uint _discipleId , uint _level) internal returns (uint) {
        if(pricePause == true) {
            if(DiscipleVendToTime[_discipleId] != 0 && DiscipleVendToTime[_discipleId] != endTime) {
                uint timePass = safeSub(endTime, startTime);
                DiscipleVending[_level] = _computePrice(DiscipleVending[_level], DiscipleVending[_level]*raiseIndex[1], preSaleDurance, timePass);
                DiscipleVendToTime[_discipleId] = endTime;
            }
            return DiscipleVending[_level];
        } else {
            if(DiscipleVendToTime[_discipleId] == 0) {
                DiscipleVendToTime[_discipleId] = uint(now);
            }
            uint currentPrice = _computePrice(DiscipleVending[_level], DiscipleVending[_level]*raiseIndex[1], preSaleDurance, safeSub(uint(now), startTime));
            return currentPrice;
        }
    }
    // ----------------------------------------------------------------------------
    // Public Function
    // ----------------------------------------------------------------------------
    function createDiscipleSale(uint _num, uint _startId, uint _occupation, uint _level, uint _price) public onlyAdmin {
        for(uint i = 0; i<_num; i++) {
            _generateDiscipleSale(_startId + i, _occupation, _level, _price);
        }
    }
    
    function buyDisciple(uint _discipleSaleId, uint _brokerId, uint _subBrokerId) public payable whenNotPaused {
        DiscipleSale storage _discipleSale = discipleSales[_discipleSaleId];
        require(DiscipleSaleToBuyer[_discipleSale.discipleId] == address(0));
        require(_discipleSale.ifSold == false);
        uint currentPrice;
        if(pricePause == true) {
            if(_discipleSale.timestamp != 0 && _discipleSale.timestamp != endTime) {
                uint timePass = safeSub(endTime, startTime);
                _discipleSale.price = _computePrice(_discipleSale.price, _discipleSale.price*raiseIndex[1], preSaleDurance, timePass);
                _discipleSale.timestamp = endTime;
            }
            _brokerFeeDistribute(_discipleSale.price, 1, _brokerId, _subBrokerId);
            require(msg.value >= _discipleSale.price);
            currentPrice = _discipleSale.price;
        } else {
            if(_discipleSale.timestamp == 0) {
                _discipleSale.timestamp = uint(now);
            }
            currentPrice = _computePrice(_discipleSale.price, _discipleSale.price*raiseIndex[1], preSaleDurance, safeSub(uint(now), startTime));
            _brokerFeeDistribute(currentPrice, 1, _brokerId, _subBrokerId);
            require(msg.value >= currentPrice);
            _discipleSale.price = currentPrice;
        }
        DiscipleSaleToBuyer[_discipleSale.discipleId] = msg.sender;
        _discipleSale.ifSold = true;
        emit BuyDisciple(_discipleSaleId, _discipleSale.discipleId, msg.sender, currentPrice);
    }
    
    function offlineDiscipleSold(uint _discipleSaleId, address _buyer, uint _price) public onlyAdmin {
        DiscipleSale storage _discipleSale = discipleSales[_discipleSaleId];
        require(_discipleSale.ifSold == false);
        DiscipleSaleToBuyer[_discipleSale.discipleId] = _buyer;
        _discipleSale.ifSold = true;
        emit BuyDisciple(_discipleSaleId, _discipleSale.discipleId, _buyer, _price);
    }
    
    function OfferToDisciple(uint _discipleSaleId, uint _price) public payable whenNotPaused {
        DiscipleSale storage _discipleSale = discipleSales[_discipleSaleId];
        require(_discipleSale.ifSold == true);
        require(_price > _discipleSale.offerPrice*11/10);
        require(msg.value >= _price);
        
        if(_discipleSale.bidder == address(0)) {
            _discipleSale.bidder = msg.sender;
            _discipleSale.offerPrice = _price;
        } else {
            address lastBidder = _discipleSale.bidder;
            uint lastOffer = _discipleSale.price;
            lastBidder.transfer(lastOffer);
            
            _discipleSale.bidder = msg.sender;
            _discipleSale.offerPrice = _price;
        }
        
        emit DiscipleOfferSubmit(_discipleSaleId, _discipleSale.discipleId, msg.sender, _price);
    }
    
    function AcceptDiscipleOffer(uint _discipleSaleId) public whenNotPaused {
        DiscipleSale storage _discipleSale = discipleSales[_discipleSaleId];
        require(DiscipleSaleToBuyer[_discipleSale.discipleId] == msg.sender);
        require(_discipleSale.bidder != address(0) && _discipleSale.offerPrice > 0);
        msg.sender.transfer(_discipleSale.offerPrice);
        DiscipleSaleToBuyer[_discipleSale.discipleId] = _discipleSale.bidder;
        _discipleSale.price = _discipleSale.offerPrice;
        
        emit DiscipleOfferAccept(_discipleSaleId, _discipleSale.discipleId, _discipleSale.bidder, _discipleSale.price);
        
        _discipleSale.bidder = address(0);
        _discipleSale.offerPrice = 0;
    }
    
    function setDiscipleSale(uint _discipleSaleId, uint _price, uint _occupation, uint _level) public onlyAdmin {
        DiscipleSale storage _discipleSale = discipleSales[_discipleSaleId];
        require(_discipleSale.ifSold == false);
        _discipleSale.price = _price;
        _discipleSale.occupation = _occupation;
        _discipleSale.level = _level;
        emit SetDiscipleSale(_discipleSaleId, _price, _occupation, _level);
    }
    
    function getDiscipleSale(uint _discipleSaleId) public view returns (
        address owner,
        uint discipleId,
        uint occupation,
        uint level,
        uint price,
        bool ifSold,
        address bidder,
        uint offerPrice,
        uint timestamp
    ) {
        DiscipleSale memory _DiscipleSale = discipleSales[_discipleSaleId];
        owner = DiscipleSaleToBuyer[_DiscipleSale.discipleId];
        discipleId = _DiscipleSale.discipleId;
        occupation = _DiscipleSale.occupation;
        level = _DiscipleSale.level;
        price = _DiscipleSale.price;
        ifSold =_DiscipleSale.ifSold;
        bidder = _DiscipleSale.bidder;
        offerPrice = _DiscipleSale.offerPrice;
        timestamp = _DiscipleSale.timestamp;
    }
    
    function getDiscipleNum() public view returns(uint) {
        return discipleSales.length;
    }
    
    function vendDisciple(uint _discipleId) public payable whenNotPaused {
        require(_discipleId > 1000 && _discipleId <= 10000);
        if(_discipleId > 1000 && _discipleId <= 2000) {
            require(DiscipleSaleToBuyer[_discipleId] == address(0));
            require(msg.value >= _discipleVendPrice(_discipleId, 0));
            DiscipleSaleToBuyer[_discipleId] = msg.sender;
            DiscipleVendToOffer[_discipleId] = DiscipleVending[0];
        } else if (_discipleId > 2000 && _discipleId <= 4000) {
            require(DiscipleSaleToBuyer[_discipleId] == address(0));
            require(msg.value >= _discipleVendPrice(_discipleId, 1));
            DiscipleSaleToBuyer[_discipleId] = msg.sender;
            DiscipleVendToOffer[_discipleId] = DiscipleVending[1];
        } else if (_discipleId > 4000 && _discipleId <= 6000) {
            require(DiscipleSaleToBuyer[_discipleId] == address(0));
            require(msg.value >= _discipleVendPrice(_discipleId, 2));
            DiscipleSaleToBuyer[_discipleId] = msg.sender;
            DiscipleVendToOffer[_discipleId] = DiscipleVending[2];
        } else if (_discipleId > 6000 && _discipleId <= 8000) {
            require(DiscipleSaleToBuyer[_discipleId] == address(0));
            require(msg.value >= _discipleVendPrice(_discipleId, 3));
            DiscipleSaleToBuyer[_discipleId] = msg.sender;
            DiscipleVendToOffer[_discipleId] = DiscipleVending[3];
        } else if (_discipleId > 8000 && _discipleId <= 10000) {
            require(DiscipleSaleToBuyer[_discipleId] == address(0));
            require(msg.value >= _discipleVendPrice(_discipleId, 4));
            DiscipleSaleToBuyer[_discipleId] = msg.sender;
            DiscipleVendToOffer[_discipleId] = DiscipleVending[4];
        }
        emit VendingDisciple(_discipleId, msg.sender);
    }
    
    function offerDiscipleVend(uint _discipleId, uint _offer) public payable whenNotPaused {
        require(DiscipleSaleToBuyer[_discipleId] != address(0));
        require(_offer >= DiscipleVendToOffer[_discipleId]*11/10);
        require(msg.value >= _offer);
        address lastBidder = DiscipleVendToBidder[_discipleId];
        if(lastBidder != address(0)){
            lastBidder.transfer(DiscipleVendToOffer[_discipleId]);
        }
        DiscipleVendToBidder[_discipleId] = msg.sender;
        DiscipleVendToOffer[_discipleId] = _offer;
        emit DiscipleVendOffer(_discipleId, msg.sender, _offer);
    }
    
    function acceptDiscipleVend(uint _discipleId) public whenNotPaused {
        require(DiscipleSaleToBuyer[_discipleId] == msg.sender);
        address bidder = DiscipleVendToBidder[_discipleId];
        uint offer = DiscipleVendToOffer[_discipleId];
        require(bidder != address(0) && offer > 0);
        msg.sender.transfer(offer);
        DiscipleSaleToBuyer[_discipleId] = bidder;
        DiscipleVendToBidder[_discipleId] = address(0);
        DiscipleVendToOffer[_discipleId] = 0;
        emit DiscipleVendAccept(_discipleId, bidder, offer);
    }
    
    function setDiscipleVend(uint _num, uint _price) public onlyAdmin {
        DiscipleVending[_num] = _price;
        emit SetDiscipleVend(_num, _price);
    }
    
    function getDiscipleVend(uint _discipleId) public view returns (
        address owner,
        address bidder,
        uint offer
    ) {
        owner = DiscipleSaleToBuyer[_discipleId];
        bidder = DiscipleVendToBidder[_discipleId];
        offer = DiscipleVendToOffer[_discipleId];
    }
}
function _generateDiscipleSale(uint _discipleId, uint _occupation, uint _level, uint _price) internal returns (uint) {
    require(DiscipleIdToIfCreated[_discipleId] == false);
    DiscipleIdToIfCreated[_discipleId] = true;
    DiscipleSale memory _DiscipleSale = DiscipleSale({
        discipleId: _discipleId,
        occupation: _occupation,
        level: _level,
        price: _price,
        ifSold: false,
        bidder: address(0),
        offerPrice: 0,
        timestamp: 0
    });
    uint discipleSaleId = discipleSales.push(_DiscipleSale) - 1;
    emit DiscipleSaleCreate(discipleSaleId, _discipleId, _price, _occupation, _level);
        
    return discipleSaleId;
}

// ----------------------------------------------------------------------------
// Modifier
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// Internal Function
// ----------------------------------------------------------------------------