/*
 * 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; version 2
 * of the License only.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
//-----------------------------------------------------------------------------
// Resume play
//-----------------------------------------------------------------------------
package net.pms.dlna;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.IOException;
//import static java.nio.file.StandardCopyOption.*;

import java.util.List;
import java.util.ArrayList;
import java.util.StringTokenizer;

import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.RealFile;
import static net.pms.util.StringUtil.convertNameToFileName;
import net.pms.dlna.virtual.VirtualFolder;
import net.pms.dlna.rz_ResumeFolder;
import net.pms.dlna.ZippedFile;
import net.pms.dlna.ZippedEntry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class rz_Resume {
	private static final Logger logger = LoggerFactory.getLogger(rz_Resume.class);
	private static PmsConfiguration config=PMS.getConfiguration();

	private long resMinTime1=30*1000;	//msec, minimum playtime to save resume, when start from top
	private long resMinTime2=6*1000;	//msec, minimum playtime to save resume, when start from mid
	private long resRewindTime=10*1000;	//msec, rewind time when resume
	private long resDurationTimeDelta=60*1000;	//msec, tolerance value on duration for judge end of play
	private boolean simpleMode=true;
	private File base_folder,watched_folder;
	private static String base_path;
	private static String watched_path;
	private static String rsuffix="."+PMS.rz_ResumeFileSuffix;
	private static int cnt0,cnt1;
	private rz_ResumeFolder rf;
	private VirtualFolder vrf;
	private int resname_pathadd_iso=PMS.getConfiguration().getRZ_resname_pathadd_iso();
	
	private static final String CRLF=System.getProperty("line.separator");
	public static final String HASH_PREFIX_WEB="@hash_web@";
	public static final String HASH_PREFIX="@hash@";
	public static final String watched_path_name="Watched";
	
	public rz_Resume() {
		//try {
			//deside resume folder
			//base_path=config.getTempFolder()+PMS.FSEP+"Resume";
			base_path=config.getUserDataFolder()+PMS.FSEP+"Resume";
			watched_path=base_path+PMS.FSEP+watched_path_name;

			if(config!=null) {
				resMinTime1=(long)(config.getRZ_resume_play_min_time1()*1000);
				resMinTime2=(long)(config.getRZ_resume_play_min_time2()*1000);
				resRewindTime=(long)(config.getRZ_resume_rewind_time()*1000);
				resDurationTimeDelta=(long)(config.getRZ_resume_duration_delta()*1000);
			}
			//PMS.dbg("rz_Resume: resMinTime1(sec)="+resMinTime1/1000.0+", resMinTime2(sec)="+resMinTime2/1000.0);
			//PMS.dbg("rz_Resume: resRewindTime(sec)="+resRewindTime/1000.0+", resDurationTimeDelta(sec)="+resDurationTimeDelta/1000.0);
		//} catch (IOException e) {
			//PMS.dbg("rz_Resume: getTempFolder IOException="+e);
		//	return ;
		//}
		
		base_folder=new File(base_path);
		if(!base_folder.isDirectory()) {
			boolean rc=base_folder.mkdir();
			if(!rc) {
				logger.error("rz_Resume: failed to create resume folder="+base_path);
			}
		}
		watched_folder=new File(watched_path);
		if(!watched_folder.isDirectory()) {
			boolean rc=watched_folder.mkdir();
			if(!rc) {
				logger.error("rz_Resume: failed to create watched folder="+watched_path);
			}
		}
	}
	
	public File getBaseFolder() {
		return base_folder;
	}
	public String getBasePath() {
		return base_path;
	}
	public File getWatchedFolder() {
		return watched_folder;
	}
	
	/*-- moved to RootFolder --
	public rz_ResumeFolder getResumeFolder0(String name) {
		//rf= new rz_PlayMetaFile(folder,name,0);
		PMS.dbg("createResumeFolder0: called, count="+(++cnt0));
		if(rf==null) {
			rf= new rz_ResumeFolder(base_folder,name,false);
		}
		return rf;
	}
	public VirtualFolder getResumeFolder(String name) {
		PMS.dbg("getResumeFolder: called, count="+(++cnt1));
		if(rf==null) {
			rf= new rz_ResumeFolder(base_folder,name,false);
		}
		VirtualFolder vf = new VirtualFolder(name, null);
		vf.rz_linkobj=rf;
		return vf;
	}
	
	public VirtualFolder getResumeFolderVf(DLNAResource pa, String name) {
		if(rf==null) {
			return null;
		}
		VirtualFolder vf = new VirtualFolder(name, null);
		vf.rz_linkobj=rf;
		return vf;
	}
	-------------*/
	
	public void playStarted(DLNAResource dlna, Double startTimeSec) {
		//PMS.dbg("rz_Resume.playStarted: startTimeSec="+startTimeSec);
		
		dlna.resStartTime=(long)(startTimeSec*1000);
		dlna.resStartRealTime=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("==== rz_Resume.playStarted: resStartTime="+dlna.resStartTime+", resStartRealTime(msec)="+dlna.resStartRealTime);
		
		if(dlna.resStartTimeForce>0) {
			//PMS.dbg("rz_Resume.playStarted: resStartTime changed to resStartTimeForce="+dlna.resStartTimeForce);
			//backward skipChapter: not to designated startTimeSec(=0), but last start_pos
			dlna.resStartTime=dlna.resStartTimeForce;
			dlna.resStartTimeForce=0;
			dlna.pauseTime=dlna.resStartTime;
		}
		//dlna.resStat=dlna.RES_START;
	}
	
	public void playStopped(DLNAResource dlna,rz_SessionInfo sess,int mode) {
		long stopRealTime;
		long playTime;
		long minTime=sess.resMinTime1;
		boolean do_save=true;
		boolean saved=false;
		boolean deleted=false;
		//mode=1: indicate playstop directly 
		//mode=2: indicate folder-refresh after playstop
		
		if(PMS.rz_debug>1) PMS.dbg("==== rz_Resume.playStopped: called, mode="+mode+", sess.resume_mode="+sess.resume_mode); 
		
		if(PMS.rz_resume_mode==0) {	//disabled
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: Noop: Resume Disabled (PMS.rz_resume_mode=0)"); 
			return;
		}
		if(sess.resume_mode!=mode) {//mode unmatch
			do_save=false;
		}
		if(mode==1) {
			stopRealTime=System.currentTimeMillis();
		}
		else { //v1.41, folder-refresh after playstop: use real stop time, not folder refresh time
			stopRealTime=dlna.resStopRealTime;
		}
		playTime=stopRealTime - dlna.resStartRealTime;
		if(playTime>1000) {
			sess.playTimeTotal+=playTime;  //total playtime from autoPlay started
			sess.playTime+=playTime;  //playtime from current selected audio
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: Total playTime(sec)="+(sess.playTime/1000.0));
		}
			
		if(PMS.rz_debug>1) {
			PMS.dbg("rz_Resume.playStopped: do_save="+do_save+", stopRealTime="+stopRealTime+", resStopRealTime="+dlna.resStopRealTime); 
			PMS.dbg("rz_Resume.playStopped: mode="+mode+", clmode="+sess.resume_mode+", playTime(sec)="+(playTime/1000.0)
				+", resStartTime="+dlna.resStartTime+", pauseTime="+dlna.pauseTime+", resumeTime="+dlna.resumeTime
				+", minTime1="+sess.resMinTime1/1000.0+",minTime2="+sess.resMinTime2/1000.0);
		}
		
		if(dlna.resStartTime>0) {
			minTime=sess.resMinTime2;
		}
		dlna.resStopRealTime=stopRealTime;
		dlna.resStat=dlna.RES_STOP;

		long newStartTime=dlna.resStartTime+playTime;
		long maxTime;
		if(dlna.getMedia()!=null && dlna.getMedia().getDuration()!=null) {
			maxTime=(long)(dlna.getMedia().getDuration()*1000);
			if(maxTime==0) maxTime=9*3600*1000;
		}
		else {
			maxTime=9*3600*1000;	// 9 hr
		}
		
		//dlna.pauseTime=newStartTime-pauseRewindTime; //pause pos for ByteSeekPause
		dlna.pauseTime=newStartTime; //pause pos for ByteSeekPause
		dlna.resumeTime=newStartTime-resRewindTime; //resume pos
		if(dlna.pauseTime<0) dlna.pauseTime=0;
		if(dlna.resumeTime<0) dlna.resumeTime=0;
		
		if(do_save) {
			if(playTime>minTime) {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: Save ResumeFile");
				resUpdate(dlna,dlna.resumeTime);
				saved=true;
				if(newStartTime>maxTime-resDurationTimeDelta) {
					if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: Played Near to end, Delete ResumeFile");
					resDelete(dlna);  //really, not detete: move to watched
					deleted=true;
				}
			}
			else {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: Not Saved (playTime <= minTime)");
			}
		}
		else {
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: resume disabled, Not Saved");
		}
		/*
		if(newStartTime>maxTime-resDurationTimeDelta) {
			if(save && playTime>minTime) {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: SaveJudge: Played Near to end, Remove ResumeFile");
				resUpdate(dlna,dlna.resumeTime);
				resDelete(dlna);
			}
		}
		else if(save) {
			if(playTime>minTime) {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.playStopped: SaveJudge: playTime > minTime, Save to ResumeFile");
				resUpdate(dlna,dlna.resumeTime);
				saved=true;
			}
			else {
			}
		}
		else {
		}
		*/
		
		if(PMS.rz_debug>1) {
			PMS.dbg("rz_Resume.playStopped:"
				+" TotalPlayTime="+dlna.pauseTime/1000.0
				+", LastPlayTime="+(playTime/1000.0)
				+", Next ResumeTime="+dlna.resumeTime/1000.0
				+", ResumeSaved="+saved
				+", ResumeDeleted="+deleted
				+", Min Playtime to ResumeSave="+(minTime/1000.0)
				+", Playtime to ResumeDelete(Near End)="+(maxTime/1000.0)+" - "+(resDurationTimeDelta/1000.0)
			);
		}
	}
	
	private void resUpdate(DLNAResource dlna, long startTime) {
		//PMS.dbg("rz_Resume.resUpdate: store Rseume, newStartTimeSec="+(startTime/1000.0));
		DLNAResource ro=getResObj(dlna);
		if(ro==null) {
			ro=addResObj(dlna);
		}
		//ro.resumeTimeSec=startTime/1000;
		resSave(ro,startTime);
	}
	
	public void resDelete (DLNAResource dlna) {
		//PMS.dbg("rz_Resume.resDelete: called dlna="+dlna);
		String ropath=getResumeObjPath(dlna);
		if(ropath!=null){
			//PMS.dbg("rz_Resume.resDelete: delete resFile="+ropath);
			if(ropath.endsWith(rsuffix)) {
				//fail-safe
				File rf=new File(ropath);
				if(rf.isFile()) {
					//rf.delete();
					res_delete(rf);
				}
			}
		}
	}
	public void resDeleteAll (List<String> srcHash_list) {
		File[] files=base_folder.listFiles();
		//PMS.dbg("resDeleteAll:called srcHash_list="+srcHash_list);
		for(File f: files) {
			String name=f.getName();
			//PMS.dbg("resDeleteAll:file="+f.getName());
			if(f.isFile() && name.endsWith(rsuffix)) {
				if(srcHash_list==null) {
					//f.delete();
					res_delete(f);
				}
				else {
					for(String hash: srcHash_list) {
						if(name.contains(hash)) {
							//PMS.dbg("resDeleteAll: exec delete, hash="+hash+", file="+f.getName());
							//f.delete();
							res_delete(f);
						}
					}
				}
			}
		}
	}
	
	public void set_watched_path(String watched_path) {
		this.watched_path=watched_path;
	}
	public void res_delete (File f) {
		if(f==null || base_path==null) return;
		if(watched_path==null) {
			//real delete
			//assert file is under base_path
			File dest=new File(base_path + PMS.FSEP + f.getName());
			dest.delete();
		}
		else {
			//move to watched_folder, instead of delete
			File dest=new File(watched_path + PMS.FSEP + f.getName());
			if(dest.exists()) {
				//on windows: renameTo will fail if target exists
				dest.delete();  //if dest is folder and not empty, then will fail
			}
			f.renameTo(dest);
		}
	}
	
	private DLNAResource getResObj(DLNAResource dlna) {
		return dlna;	//fake
	}
	
	private DLNAResource addResObj(DLNAResource dlna) {
		return (DLNAResource) null;
	}
	
	private void resSave(DLNAResource ro, long startTime) {
		//create pxm for resume
		String s;
		OutputStreamWriter out;

		
		//PMS.dbg("resSave: called");
		if(PMS.rz_resume_mode==0) {
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: Noop, Resume disabled");
			return;
		}
		if(ro instanceof ZippedEntry) {
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: Noop, ZippedEntry");
			return;
		}
		if(ro.getExt()!=null) {  //real file
			if(!ro.getExt().isVideo()) {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: Noop, Seems to be not Vodeo");
				return;
			}
		}
		else { //seems web contents
			if(ro.sotype!=DLNAResource.SO_VIDEO_STREAM) {
				if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: Noop, Seems to be WEBcontents, and not WebVodeo");
				return;
			}
		}

		//get target file path
		String tgpath=ro.getSrcPath();
		if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: target file path="+tgpath);
		if(tgpath==null) {
			if(PMS.rz_debug>1) PMS.dbg("rz_Resume.resSave: not saved, target path=null");
			return;
		}

		/*
		if(ro.meta_type==2 && ro.rz_Metafile!=null) { //resume file it'self
			ropath=ro.rz_Metafile.getNam();
		}
		else {
			ropath=getResumeObjPath(ro);
		}
		*/
		String ropath=getResumeObjPath(ro);
		
		if(ropath==null) return;
		
		try {
			FileOutputStream o = new FileOutputStream(ropath);
			//out = new OutputStreamWriter(o,config.getRZ_MetafileEncode());
			out = new OutputStreamWriter(o,"UTF-8");	//use fixed encode(utf-8)
		} catch (IOException e) {
			logger.warn("rz_Resume.resSave: File Open Error IOException="+e);
			return ;
		}
		
		try {
			String metafile=null;
			if(ro.rz_Profile!=null) {
				metafile=ro.rz_Profile.getPath();
			}
			if(ro.rz_Metafile2!=null) {
				metafile=ro.rz_Metafile2.getPath();
			}
			else if(ro.rz_Metafile!=null) {
				metafile=ro.rz_Metafile.getPath();
			}
			if(metafile!=null && !metafile.equals(ropath)) {
				out.write("metafile="+"\""+metafile+"\""+CRLF);
			}
			if(ro.sotype==DLNAResource.SO_VIDEO_STREAM) {
				out.write("vstream="+"\""+tgpath+"\""+CRLF);
				if(ro.wmed_duration>0) {
					out.write("duration="+ro.wmed_duration+CRLF);
				}
				String web_opts=ro.getUrlParamsString();
				//PMS.dbg("web_opts="+web_opts);
				if(web_opts!=null) {
					out.write("web_opts="+web_opts+CRLF);
				}
			}
			else
				out.write("path="+"\""+tgpath+"\""+CRLF);
			
			out.write("start_pos="+(startTime/1000.0)+CRLF);
			
			if(false) {
				out.write("title="+ro.getName()+CRLF);
			}
			else {   // moved to rz_PlayMetafile.getDisplayName()
				String pathadd=null;
				DLNAResource pa=null;
				if(resname_pathadd_iso<0 && ro instanceof DVDISOTitle && ro.sotype!=DLNAResource.SO_DVDDEV) {	//DVDISO file
					//make Title= {DVDISO Filename}/TitleN
					if(resname_pathadd_iso<0) {
						pa=ro.getParent();
						pathadd=pa.getName();
					}
					if(resname_pathadd_iso<-1) {
						pa=pa.getParent();
						pathadd=pa.getName()+"/"+pathadd;
					}
				}
				if(pathadd!=null) {
					out.write("title="+pathadd+"/"+ro.getName0()+CRLF);
				}
				else {
					out.write("title="+ro.getName0()+CRLF);
				}
			}

			//PMS.dbg("resSave: write title="+ro.getName());
			
			if(ro.getTitleNum()>0) {
				out.write("dvd_title_num="+ro.getTitleNum()+CRLF);
			}
			out.close();
		} catch (IOException e) {
			logger.warn("rz_Resume.resSave: Write Error IOException="+e);
			return;
		}
	}
	
	public static String getResumeFileName(File file,DLNAResource dlna) {
		//resume file name format
		if(file==null || file.getParentFile()==null) return null;
		String name=""+file.getParentFile().hashCode();
		if(dlna!=null && dlna instanceof WebStream) {
			//raw url name is not smart, use title  
			if(dlna.isUnderWebFolder()) {
				//name=convertNameToFileName(dlna.getName0())+" hash_web_"+name+rsuffix;
				name=convertNameToFileName(dlna.getName0())+ HASH_PREFIX_WEB +name+rsuffix;
			}
			else {
				//name=convertNameToFileName(dlna.getName0())+" hash_"+name+rsuffix;
				name=convertNameToFileName(dlna.getName0())+ HASH_PREFIX + name+rsuffix;
			}
		}
		else {
			name=convertNameToFileName(file.getName())+ HASH_PREFIX +name+rsuffix;
		}
		return name;
	}
	
	public static String getResumeObjName(DLNAResource dlna) {
		String rpath=getResumeObjPath(dlna);
		//File f= new(rpath);
		//return f.getName();
		if(rpath==null) return null;
		return rpath.substring(base_path.length()+1);
	}
	
	public static String getResumeObjPath(DLNAResource dlna) {
		//get target resume_file path
		String ropath;
		String name;
		File bf=null,pf=null;
		String hashstr=null;
		
		if(dlna.rz_Metafile!=null) {	//metafilef 
			bf=dlna.rz_Metafile;  //default is metafile's path, not body_path
			if(dlna.meta_type==2) {  //resume file
				if(bf.getParent().equals(base_path)) {  //on resume_folder
					return bf.getPath();  //return same path
				}
				else if(bf.getParent().equals(watched_path)){ //on watched_folder
					return base_path+PMS.FSEP+bf.getName();  //force on resume_folder
				}
			}
		}
		else if(dlna instanceof DVDISOTitle) {	
			name=convertNameToFileName(dlna.getName());
			if(dlna.sotype==DLNAResource.SO_DVDDEV) {  //rea; DVD dev
				//dvd, bpath(ex. F:/VIDEO_TS) will not contain file_name part 
				//set resume_folder to DVDDEV
				String path=dlna.getResumePath()+PMS.FSEP+dlna.getLastmodified()+PMS.FSEP+name;
				bf=new File(path);
			}
			else {  //DVD ISO file
				String path=dlna.getResumePath()+PMS.FSEP+name;
				bf=new File(path);
			}
		}
		else {  //normal files
			String bpath=dlna.getResumePath();
			if(bpath!=null) {
				bf=new File(bpath);
			}
		}
		if(PMS.rz_debug>1) PMS.dbg("rz_Resume.getResumeObjPath: target file path="+bf);
		
		name=getResumeFileName(bf,dlna);
		if(name==null) return null;
		ropath=base_path+PMS.FSEP+name;
		return ropath;
	}
	
	public void resSaveFile() {
		
	}
	
	public void resLoadFile() {
		
	}

}
