/*
 * Copyright 2006-2025 www.anyline.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.anyline.net;
 
import org.anyline.util.ConfigTable;
import org.anyline.util.DateUtil;
import org.anyline.util.FileUtil;
import org.anyline.log.Log;
import org.anyline.log.LogProxy;

import java.io.File;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
 
public class DownloadTask {
	private static final Log log = LogProxy.get(DownloadTask.class);
	private boolean openLog = true; 
	private String url	; // url
	private File local	; // 本地文件
	private int threads	; // 线程数量
	private long past	; // 上次已下载长度
	private long length	; // 本次需下载长度
	private long finish ; // 本次已下载长度
	private long start	; // 开始时间
	private long end	; // 结束时间
	private Map<String, String> headers;
	private Map<String, Object> params;
	private Map<String, Object> extras = new HashMap<String, Object>();	// 扩展属性(回调时原样返回)
	private int index		; // 任务下标从0开始
	private long expend		; // 本次已耗时
	private long expect		; // 本次预计剩余时间
	private double rate = 0 ; // 完成比例
 
	private double lastLogRate	; // 最后一次日志进度
	private long lastLogTime	; // 量后一次日志时间
	private String errorMsg = ""; // 异常信息
	private int errorCode = 0	; // 异常编号
	private int action =1		; // 1正常执行 0中断
	private int status = 0		; // 0初始 1执行中 2暂停 -1异常 9已完成
	 
	private Map<Long, Long> records = new LinkedHashMap<Long, Long>(); // 下载记录
	private DownloadProgress progress = new DefaultProgress(); 
	private DownloadListener listener; 
	private boolean override = false; 
	 
	/** 
	 * 是否覆盖已存在文件 
	 * @return boolean
	 */ 
	public boolean isOverride() {
		return override; 
	} 
 
	public void setOverride(boolean override) {
		this.override = override; 
	} 
 
	public DownloadTask() {
	} 
 
	/** 
	 * 平均每秒下载byte 
	 * @return long
	 */ 
	public long getAvgSpeed() {
		Long sum = 0L; 
		Long fr = 0L; 
		Iterator<Entry<Long, Long>> entries = records.entrySet().iterator();
		while (entries.hasNext()) {
			Map.Entry<Long, Long> entry =  entries.next();
			Long key = entry.getKey(); // 记录时间
			Long value = entry.getValue();//记录值 
			if(System.currentTimeMillis() - key> 1000*10) {//10秒内的值
		    	entries.remove(); 
		    }else{
		    	sum += value; 
		    	if(key < fr || fr ==0) {
		    		fr = key; 
		    	} 
		    } 
		} 
		long time =System.currentTimeMillis(); 
		if(time >0) {
			return sum*1000/(time); 
		}else{
			return sum*10000; 
		} 
	} 
	/** 
	 * 瞬时每秒下载byte(只计算最后一次) 
	 * @return long
	 */ 
	public long getSpeed() {
		if(!this.isRunning()) {
			return 0L; 
		} 
		Long len = 0L;	// 最后一次下载长度 
		Long time = 0L; 
		Long curTime = 0L; 
		Iterator<Entry<Long, Long>> entries = records.entrySet().iterator();
		while (entries.hasNext()) {
			Map.Entry<Long, Long> entry =  entries.next();
			Long key = entry.getKey(); // 记录时间
			Long value = entry.getValue();//记录值 
			curTime = key; 
			// 最后一组 
			if(!entries.hasNext()) {
				len = value; 
				time = key - curTime; 
			} 
		} 
		if(time >0) {
			return len*1000/(time); 
		}else{
			return len*10000; 
		} 
	} 
	/** 
	 * 平均下载速度/s 
	 * @return String
	 */ 
	public String getAvgSpeedFormat() {
		long speed = getSpeed(); 
		return FileUtil.length(speed)+"/s"; 
	} 
	/** 
	 * 瞬时下载速度/s 
	 * @return String
	 */ 
	public String getSpeedFormat() {
		long speed = getSpeed(); 
		return FileUtil.length(speed)+"/s"; 
	} 
	public DownloadTask(String url, File local) {
		this.url = url; 
		this.local = local; 
	} 
	public DownloadTask(String url, File local, Map<String, String> headers, Map<String, Object> params, Map<String, Object> extras) {
		this.url = url; 
		this.local = local; 
		this.headers = headers; 
		this.params = params; 
		this.extras = extras; 
	} 
	public DownloadTask(String url, File local, Map<String, String> headers, Map<String, Object> params) {
		this.url = url; 
		this.local = local; 
		this.headers = headers; 
		this.params = params; 
	} 
 
	public void init(long length, long past) {
		this.length = length; 
		this.past = past; 
		this.start = System.currentTimeMillis(); 
		this.status = 0; 
	} 
	public void step(long len) {
		this.finish +=  len; 
		rate = new BigDecimal((this.finish+this.past)*100.0/(this.length+this.past)).setScale(2, BigDecimal.ROUND_HALF_DOWN).doubleValue(); // 完成比例
 
    	if(this.finish >= length) {
    		this.finish = length; 
    		rate = 100.00; 
    		end = System.currentTimeMillis(); 
    	} 
    	if(rate ==100 && finish < length) {
    		rate = 99.99; 
    	} 
    	if(end ==0) {
    		expend = System.currentTimeMillis() - start;//已耗时 
    	}else{
    		expend = end - start;//已耗时 
    	} 
		if(expend>0) {
			expect = (long)(length / (this.finish*1.0/expend) - expend);	// 剩余时间=预计总耗时-已耗时 
			if(rate == 100) {
				expect = 0; 
			} 
			if(ConfigTable.IS_DEBUG && openLog) {
				log(); 
			} 
		} 
		records.put(System.currentTimeMillis(), len);
		if(rate >=100) {
			finish(); 
		} 
	} 
	public void error(int code, String message) {
		try{
			status = -1; 
			log.error("[文件下载][下载异常][url:{}][code:{}][message:{}]", url, code, message);
			this.errorCode = code; 
			this.errorMsg = message; 
			if(null != listener) {
				listener.error(this); 
			} 
		}catch(Exception ignored) {
		} 
	} 
	public void finish() {
		try{
			status = 9; 
			this.rate = 100.00; 
			this.end = System.currentTimeMillis(); 
			log(); 
			if(ConfigTable.IS_DEBUG && log.isWarnEnabled()) {
				log.info("[文件下载]"+"[下载完成][耗时:{}][url:{}][local:{}]", getExpendFormat(), url, getLocal().getAbsolutePath());
			} 
			if(null != listener) {
				listener.finish(this); 
			} 
		}catch(Exception e) {
			log.error("finish exception:", e);
		} 
	} 
	private void log() {
		if(!ConfigTable.IS_DEBUG && !openLog) {
			return; 
		} 
		if(getExpend() ==0) {
			return; 
		} 
		double rate = getFinishRate(); 
		// 进度>0.5%或时间超过5秒或全部完成 
		if(openLog) {
			if(lastLogTime==0 || rate - lastLogRate  >= 0.5 || System.currentTimeMillis() - lastLogTime > 1000 * 5 || rate==100) {
				log.debug("[文件下载]"+getMessage());
	    		lastLogRate = rate; 
	    		lastLogTime = System.currentTimeMillis(); 
			} 
		} 
	} 
	 
	public void start() {
		if(!isRunning() &&  action == 1) {
			action = 1; 
			status = 1; 
			if(start ==0) {
				start = System.currentTimeMillis(); 
			}
			HttpBuilder.init().addDownloadTask(this).build().download();
		}else{
			action = 1; 
			status = 1; 
		} 
	} 
	/** 
	 * 停止下载任务 
	 */ 
	public void stop() {
		action =0; 
		status = 2; 
	} 
	public int getAction() {
		return action; 
	} 
//	public boolean isRunning() {
//		if(end !=0 && getExpend()>0) {
//			return true; 
//		} 
//		return false; 
//	} 
	public double getFinishRate() {
		return this.rate; 
	} 
	public String getMessage() {
		// "[进度:10.12mb/200.11mb(20%)][数量:1/5][耗时:1分3秒/12分2秒][网速:100kb/s]" 
		String msg = "[进度:"; 
		if(this.past>0) {
			msg += getPastFormat(); 
			if(this.finish>0) {
				 msg += "+"; 
			} 
		} 
		if(this.finish>0) {
			msg += getFinishFormat(); 
		} 
		msg += "/"+getTotalFormat()+"("+getFinishRate()+"%)]" 
				+ "[耗时:"+getExpendFormat()+"/"+getExpectFormat()+"][瞬时:"+getSpeedFormat()+"/平均:"+getAvgSpeedFormat()+"][url:"+url+"][local:"+local.getAbsolutePath()+"]"; 
		return msg; 
	} 
	public String getUrl() {
		return url; 
	} 
 
	public void setUrl(String url) {
		this.url = url; 
	} 
 
	public File getLocal() {
		return local; 
	} 
 
	public void setLocal(File local) {
		this.local = local; 
	} 
 
	public int getThreads() {
		return threads; 
	} 
 
	public void setThreads(int threads) {
		this.threads = threads; 
	} 
 
	public long getLength() {
		return length; 
	} 
	public String getLengthFormat() {
		return FileUtil.length(getLength()); 
	} 
	public void setLength(long length) {
		this.length = length; 
	} 
	public long getTotal() {
		return length + past; 
	} 
	public String getTotalFormat() {
		return FileUtil.length(getTotal()); 
	} 
 
	public long getFinish() {
		return finish; 
	} 
	public String getFinishFormat() {
		return FileUtil.length(getFinish()); 
	} 
	 
 
	public void setFinish(long finish) {
		this.finish = finish; 
	} 
 
	public long getStart() {
		return start; 
	} 
 
	public void setStart(long start) {
		this.start = start; 
	} 
 
	public long getEnd() {
		return end; 
	} 
 
	public void setEnd(long end) {
		this.end = end; 
	} 
 
	public Map<String, Object> getExtras() {
		return extras; 
	} 
	public void setExtras(Map<String, Object> extras) {
		this.extras = extras; 
	} 
	public void addExtras(String key, Object value) {
		extras.put(key, value);
	} 
	/** 
	 * 耗时 
	 * @return long
	 */ 
	public long getExpend() {
		if(end ==0) {
			expend = System.currentTimeMillis() - start; 
		}else{
			expend = end - start; 
		} 
		return expend; 
	}

	/**
	 * 耗时
	 * @return String
	 */
	public String getExpendFormat() {
		return DateUtil.conversion(getExpend()); 
	} 
	public void setExpend(long expend) {
		this.expend = expend; 
	} 
	/** 
	 * 预计剩余时间 
	 * 没有实际速度时, 使用预计速度
	 * @param speed speed
	 * @return long
	 */ 
	public long getExpect(long speed) {
		if(expend>0) {
			expect = (long)(length / (this.finish*1.0/expend) - expend);	// 剩余时间=预计总耗时-已耗时 
			if(rate == 100) {
				expect = 0; 
			} 
		}else if(speed>0) {
			expect = (long)(length*1.0 / speed - expend);	// 剩余时间=预计总耗时-已耗时 
		} 
		return expect; 
	}

	/**
	 * 预计剩余时间
	 * @return long
	 */
	public long getExpect() {
		return expect; 
	} 
	public String getExpectFormat(long speed) {
		return DateUtil.conversion(getExpect(speed)); 
	} 
	public String getExpectFormat() {
		return DateUtil.conversion(getExpect()); 
	} 
	public void setExpect(long expect) {
		this.expect = expect; 
	} 
 
	public Map<String, String> getHeaders() {
		return headers; 
	} 
 
	public void setHeaders(Map<String, String> headers) {
		this.headers = headers; 
	} 
 
	public Map<String, Object> getParams() {
		return params; 
	} 
 
	public void setParams(Map<String, Object> params) {
		this.params = params; 
	} 
	public void addParam(String key, String value) {
		if(null == params) {
			params = new LinkedHashMap<String, Object>();
		} 
		params.put(key, value);
	} 
//	public boolean isFinish() {
//		return rate == 100; 
//	} 
	public int getIndex() {
		return index; 
	} 
	public void setIndex(int index) {
		this.index = index; 
	} 
	public long getPast() {
		return past; 
	} 
	public String getPastFormat() {
		return FileUtil.length(getPast()); 
	} 
	public void setPast(long past) {
		this.past = past; 
	} 
	public double getRate() {
		return rate; 
	} 
	public void setRate(double rate) {
		this.rate = rate; 
	} 
	public void openLog() {
		this.openLog = true; 
	} 
	public void closeLog() {
		this.openLog = false; 
	} 
 
	public DownloadProgress getProgress() {
		return progress; 
	} 
 
	public void setProgress(DownloadProgress progress) {
		this.progress = progress; 
	} 
 
	public String getErrorMsg() {
		return errorMsg; 
	} 
 
	public void setErrorMsg(String errorMsg) {
		this.errorMsg = errorMsg; 
	} 
 
	public int getErrorCode() {
		return errorCode; 
	} 
 
	public void setErrorCode(int errorCode) {
		this.errorCode = errorCode; 
	} 
 
	public void setListener(DownloadListener listener) {
		this.listener = listener; 
	} 
 
	public int getStatus() {
		return status; 
	} 
	public boolean isInit() {
		return status == 0; 
	} 
	public boolean isStop() {
		return status == 2; 
	} 
	public boolean isRunning() {
		return status == 1; 
	} 
	public boolean isError() {
		return status == -1; 
	} 
	public boolean isFinish() {
		return status == 9; 
	} 
} 
