/*
    par.* - parity file handling
    Copyright (C) 2003  Matthew Mueller <donut AT dakotacom.net>

    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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef NGET__PAR_H__
#define NGET__PAR_H__

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <set>
#include "myregex.h"
#include "auto_map.h"
#include "cache.h"
#include "par2/par2cmdline.h"

typedef multimap<string, string> t_nocase_map;
bool par2file_get_sethash(const string &filename, string &sethash);
bool parfile_get_sethash(const string &filename, string &sethash);
bool parfile_ok(const string &filename, uint32_t &vol_number);
int parfile_check(const string &filename, const string &path, const t_nocase_map &nocase_map);


// Sort incomplete files to the end of the list, so we try them last.
typedef pair<float, time_t> t_server_file_key;
inline t_server_file_key server_file_key(const c_nntp_file::ptr &f) {
	float completeness = (f->req >= 1) ? f->have/(float)f->req : 1.0;
	return t_server_file_key(-completeness, f->badate());
}

typedef multimap<t_server_file_key, c_nntp_file::ptr> t_server_file_list;
#define server_file_list_value(f) t_server_file_list::value_type(server_file_key(f), f)

class ParSetInfo {
	protected:
		t_server_file_list serverpars;
		t_server_file_list serverpxxs;
	public:
		void addserverpar(const c_nntp_file::ptr &f) {
			serverpars.insert(server_file_list_value(f));
		}
		void addserverpxx(const c_nntp_file::ptr &f) {
			serverpxxs.insert(server_file_list_value(f));
		}
		int get_initial_pars(c_nntp_files_u &fc, const string &path, const string &temppath) {
			int count=0;
			for (t_server_file_list::const_iterator spi=serverpars.begin(); spi!=serverpars.end(); ++spi){
				fc.addfile(spi->second, path, temppath, false);
				count++;
			}
			if (serverpars.empty() && !serverpxxs.empty()){
				t_server_file_list::iterator smallest = serverpxxs.begin(), cur = smallest;
				for (++cur; cur!=serverpxxs.end(); ++cur)
					if (cur->second->bytes() < smallest->second->bytes())
						smallest = cur;
				fc.addfile(smallest->second, path, temppath, false);
				count++;
				serverpxxs.erase(smallest);
			}
			serverpars.clear();
			return count;
		}
		void release_unclaimed_pxxs(vector<c_nntp_file::ptr> &unclaimedfiles){
			for (t_server_file_list::const_iterator spi=serverpxxs.begin(); spi!=serverpxxs.end(); ++spi){
				unclaimedfiles.push_back(spi->second);
			}
			serverpxxs.clear();
		}
};


typedef auto_multimap<string, c_regex_r> t_subjmatches_map;
typedef map<string, set<string> > t_basenames_map;
typedef map<string, vector<string> > t_basefilenames_map;
class LocalParFiles {
	public:
		t_subjmatches_map subjmatches; // maps sethash -> subject matches for known basenames of that set
		t_basenames_map basenames; // maps sethash -> known basenames for that set
		t_basefilenames_map basefilenames; // maps sethash -> all known filenames for that set
		set<string> badbasenames; // set of all basenames for which corrupt/non-par files were found
		

		void addsubjmatch_par1(const string &key, const string &basename){
			if (basenames[key].find(basename)!=basenames[key].end())
				return;//only insert one subject match for each basename
			basenames[key].insert(basename);

			string matchstr;
			if (isalnum(basename[0]))
				matchstr+=regex_match_word_beginning_safe();
			regex_escape_string(basename, matchstr);
			matchstr+="\\.p(ar|[0-9]{2})";
			matchstr+=regex_match_word_end();
			subjmatches.insert_value(key, new c_regex_r(matchstr.c_str(), REG_EXTENDED|REG_ICASE));
		}
		void addsubjmatch_par2(const string &key, const string &basename){
			if (basenames[key].find(basename)!=basenames[key].end())
				return;//only insert one subject match for each basename
			basenames[key].insert(basename);

			string matchstr;
			if (isalnum(basename[0]))
				matchstr+=regex_match_word_beginning_safe();
			regex_escape_string(basename, matchstr);
			matchstr+="(\\.vol([0-9]+)\\+([0-9]+))?\\.par2";
			matchstr+=regex_match_word_end();
			subjmatches.insert_value(key, new c_regex_r(matchstr.c_str(), REG_EXTENDED|REG_ICASE));
		}
		void addfrompath_par1(const string &path, t_nocase_map *nocase_map=NULL);
		void addfrompath_par2(const string &path, t_nocase_map *nocase_map=NULL);
		void check_badbasenames(void);
		void clear(void){
			basefilenames.clear();
			basenames.clear();
			subjmatches.clear();
			badbasenames.clear();
		}
};


class ParXInfoBase {
	protected:
		t_server_file_list serverpars;
		t_server_file_list serverpxxs;
		typedef map<string, ParSetInfo> t_parset_map;
		set<string> finished_parsets; // which sethashes we have finished (either tested ok, or exhausted all hope)
		int finished_okcount; // how many of the finished_parsets were ok
		t_parset_map parsets;
		LocalParFiles localpars;
		const string path;
		const string temppath;

		ParSetInfo *parset(const string &basename) { 
			return &parsets[basename]; // will insert a new ParSetInfo into parsets if needed.
		}
	
	public:
		ParXInfoBase(const string &p,const string &t):finished_okcount(0),path(p),temppath(t){//well, saving and using only the first temppath encountered isn't exactly perfect, but I doubt many people really download multiple things to the same path but with different temp paths.  And I'm lazy.
		}
		int get_initial_pars(c_nntp_files_u &fc);
		int getNumFinished(void) const { return finished_parsets.size(); }
		int getNumFinishedOk(void) const { return finished_okcount; }
};

class Par1Info : public ParXInfoBase {
	protected:
		int get_pxxs(int num, set<uint32_t> &havevols, const string &key, c_nntp_files_u &fc);
	public:
		Par1Info(const string &p,const string &t):ParXInfoBase(p,t){
			localpars.addfrompath_par1(path);
		}
		bool maybe_add_parfile(const c_nntp_file::ptr &f, bool want_incomplete);
		int maybe_get_pxxs(c_nntp_files_u &fc);
};

class Par2Info : public ParXInfoBase {
	protected:
		int get_extradata(int num, c_nntp_files_u &fc, const Par2Repairer *par2);
		int get_recoverypackets(int num, set<uint32_t> &havepackets, const string &key, c_nntp_files_u &fc, const Par2Repairer *par2);
		t_server_file_list serverextradata;
	public:
		Par2Info(const string &p,const string &t):ParXInfoBase(p,t){
			localpars.addfrompath_par2(path);
		}
		bool maybe_add_parfile(const c_nntp_file::ptr &f, bool want_incomplete);
		int maybe_get_pxxs(c_nntp_files_u &fc);
};

class ParInfo {
	protected:
		const string path;
		Par1Info par1info;
		Par2Info par2info;

	public:
		ParInfo(const string &p,const string &t):path(p), par1info(p,t), par2info(p,t) {
		}
		bool maybe_add_parfile(const c_nntp_file::ptr &f, bool want_incomplete) {
			return par1info.maybe_add_parfile(f, want_incomplete) || par2info.maybe_add_parfile(f, want_incomplete);
		}
		void get_initial_pars(c_nntp_files_u &fc) {
			par1info.get_initial_pars(fc);
			par2info.get_initial_pars(fc);
		}
		void maybe_get_pxxs(c_nntp_files_u &fc) {
			int total_added=0;
			total_added += par1info.maybe_get_pxxs(fc);
			total_added += par2info.maybe_get_pxxs(fc);
			if (!total_added) {
				PMSG("autopar in %s done (%i/%i parsets ok)", path.c_str(),
						par1info.getNumFinishedOk()+par2info.getNumFinishedOk(),
						par1info.getNumFinished()+par2info.getNumFinished());
			}
		}
};

class ParHandler {
	protected:
		typedef auto_map<string, ParInfo> t_parinfo_map;
		t_parinfo_map parinfos;

		t_parinfo_map::mapped_type parinfo(const string &path, const string &temppath);
		t_parinfo_map::mapped_type parinfo(const string &path);
	public:
		bool maybe_add_parfile(const c_nntp_file::ptr &f, const string &path, const string &temppath, bool want_incomplete);
		void get_initial_pars(c_nntp_files_u &fc);
		void maybe_get_pxxs(const string &path, c_nntp_files_u &fc);
};

#endif
