package com.aelitis.azureus.core.speedmanager.impl.v2;

/**
 * Created on Jul 9, 2007
 * Created by Alan Snyder
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * <p/>
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

public class LimitControlDropUploadFirst implements LimitControl
{

    private float valueUp=0.5f;//number between 0.0 - 1.0
    int upMax;
    int upCurr;
    int upMin;
    SaturatedMode upUsage;

    private float valueDown=1.0f;
    int downMax;
    int downCurr;
    int downMin;
    SaturatedMode downUsage;


    TransferMode mode;

    float usedUpMaxDownloadMode=0.6f;

    boolean isDownloadUnlimited = false;

    //  Here is how the LimitControl will handle the "unlimited" case.
    // #1) Download is allowed to be unlimited.
    // #2) Upload is not allowed to be unlimited.

    // a) Only the isDownloadUlimited boolean and upCurr value - used for unlimited case.
    // b) In upload unlimited. valueDown is set to 1.0.
    // c) upMax and upMin values keep there non-zero values.



    public void updateStatus(int currUpLimit, SaturatedMode uploadUsage,
                             int currDownLimit, SaturatedMode downloadUsage,
                             TransferMode transferMode){
        upCurr = currUpLimit;
        upUsage = uploadUsage;
        downCurr = currDownLimit;
        downUsage = downloadUsage;

        mode=transferMode;
    }

    public void setDownloadUnlimitedMode(boolean isUnlimited) {
        isDownloadUnlimited = isUnlimited;
        if(isUnlimited){
            valueDown = 1.0f;
        }
    }

    public boolean isDownloadUnlimitedMode(){
        return isDownloadUnlimited;
    }


    public void updateLimits(int _upMax, int _upMin, int _downMax, int _downMin){
        
        //verify the limits.
        if(_upMax < SMConst.START_UPLOAD_RATE_MAX ){
            _upMax = SMConst.START_UPLOAD_RATE_MAX;
        }
        if(_downMax < SMConst.START_DOWNLOAD_RATE_MAX){
            _downMax = SMConst.START_DOWNLOAD_RATE_MAX;
        }

        if(_downMax<_upMax){
            _downMax=_upMax;
        }

        _upMin = SMConst.calculateMinUpload(_upMax);
        _downMin = SMConst.calculateMinDownload(_downMax);


        upMax = _upMax;
        upMin = _upMin;
        downMax = _downMax;
        downMin = _downMin;
    }


    private int usedUploadCapacity(){

        float usedUpMax = upMax;
        if( mode.getMode() == TransferMode.State.SEEDING ){
            usedUpMax = upMax;
        }else if( mode.getMode()==TransferMode.State.DOWNLOADING ){
            usedUpMax = upMax*usedUpMaxDownloadMode;
        }else if( mode.getMode()==TransferMode.State.DOWNLOAD_LIMIT_SEARCH ){
            usedUpMax = upMax*usedUpMaxDownloadMode;
        }else if( mode.getMode()==TransferMode.State.UPLOAD_LIMIT_SEARCH ){    
            usedUpMax = upMax;
        }else{

            SpeedManagerLogger.trace("LimitControlDropUploadFirst -> unrecognized transfer mode. ");
        }

        return Math.round( usedUpMax );
    }

    public void updateSeedSettings(float downloadModeUsed)
    {
        if( downloadModeUsed < 1.0f && downloadModeUsed > 0.1f){
            usedUpMaxDownloadMode = downloadModeUsed;
            SpeedManagerLogger.trace("LimitControlDropUploadFirst %used upload used while downloading: "+downloadModeUsed);
        }
    }

    public SMUpdate adjust( float amount ){

        boolean increase = true;
        if( amount<0.0f ){
            increase = false;
        }

        float factor = amount/10.0f;
        int usedUpMax = usedUploadCapacity();
        float gamma = (float) usedUpMax/downMax;

        if( increase ){
            //increase download first
            if( valueDown<0.99f ){
                valueDown = calculateNewValue(valueDown,factor);
            }else{
                //only increase upload if used.
                if( upUsage==SaturatedMode.AT_LIMIT ){
                    valueUp = calculateNewValue(valueUp,gamma*0.5f*factor);
                }
            }
        }else{
            //decrease upload first
            if( valueUp > 0.01f){
                valueUp = calculateNewValue(valueUp,gamma*factor);
            }else{
                valueDown = calculateNewValue(valueDown,factor);
            }
        }

        return update();
    }//adjust

    private SMUpdate update(){
        int upLimit;
        int downLimit;

        int usedUpMax = usedUploadCapacity();


        upLimit = Math.round( ((usedUpMax-upMin)*valueUp)+upMin );

        //ToDo: remove diagnotics later.
        if( upLimit>upMax || Float.isNaN(valueUp)){
            SpeedManagerLogger.trace("Limit - should upload have an unlimited condition? Setting to usedUpMax");
            upLimit = usedUpMax;
        }

        if(isDownloadUnlimited){
            downLimit = SMConst.RATE_UNLIMITED;  //zero means to unlimit the download rate.
        }else{
            downLimit = Math.round( ((downMax-downMin)*valueDown)+downMin );
        }

        //Don't show a download limit unless it is needed.
        if( valueDown == 1.0 ){
            downLimit = SMConst.RATE_UNLIMITED; //only apply a limit to the download when needed.
        }
        
        //log this change.
        StringBuffer msg = new StringBuffer(" create-update: valueUp="+valueUp+",upLimit="+upLimit+",valueDown=");
        if(valueDown== 1.0 ) msg.append("_unlimited_");
        else msg.append(valueDown);
        msg.append(",downLimit="+downLimit+",upMax="+upMax+",usedUpMax="+usedUpMax+",upMin="+upMin+",downMax="+downMax);
        msg.append(",downMin="+downMin+",transferMode="+mode.getString()+",isDownUnlimited="+isDownloadUnlimited);

        SpeedManagerLogger.log( msg.toString() );

        return new SMUpdate(upLimit,true,downLimit,true);
    }

    private float calculateNewValue(float curr, float amount){

        if(Float.isNaN(curr)){
            SpeedManagerLogger.trace("calculateNewValue - curr=NaN");
        }
        if(Float.isNaN(amount)){
            SpeedManagerLogger.trace("calculateNewValue = amount=NaN");
        }

        curr += amount;
        if( curr > 1.0f){
            curr = 1.0f;
        }
        if( curr < 0.0f ){
            curr = 0.0f;
        }

        if(Float.isNaN(curr)){
            curr=0.0f;
        }

        return curr;
    }

}
