classdef Survival
	%SURVIVAL This class represent the survival of a cloud resource
	
	properties
		period;
		price;
		timestamp;
	end
	
	methods
		function [this] = Survival(file)
		% SURVIVAL create an instance of Survival based on the given file
		% file: CSV file containing price information
			[readDate, readPrice] = textread(file, '%s%f\n','delimiter',','); %#ok<DTXTRD>
			ts = datenum(readDate, 'yyyy-mm-dd HH:MM:ss');
			ts = ts*24; % convert days to hours
			this.period = ts(2:end) - ts(1:(end-1));
			this.price = readPrice(1:(end-1));
			this.timestamp = ts(1:end-1);
		end
		
		function [mergedPeriod, sumPrice] = mergePeriods(this, bid)
			% MERGEDPERIODS
			% bid: cutoff bid. All the period less than bid and large than bid
			% will be merged
			% mergedPeriod: duration of a merged period
			% sumPrice: sum of the prices of the merged period
			
			mergedPeriod = this.period;
			sumPrice = this.period.*this.price;
			jj = 1; % merged periods
			
			for ii=2:length(this.period)
				if this.price(ii)<=bid && sumPrice(jj)<=bid*mergedPeriod(jj)
					% current price is ok, merged period is ok, just add it
					mergedPeriod(jj) = mergedPeriod(jj) + this.period(ii);
					sumPrice(jj) = sumPrice(jj) + this.price(ii).*this.period(ii);
				elseif this.price(ii)>bid && sumPrice(jj)>bid*mergedPeriod(jj)
					% current price is overbidded, merged period is overbidden, just
					% add it
					mergedPeriod(jj) = mergedPeriod(jj) + this.period(ii);
					sumPrice(jj) = sumPrice(jj) + this.price(ii).*this.period(ii);
				elseif this.price(ii)<=bid && sumPrice(jj)>bid*mergedPeriod(jj)
					% current price is ok, merged period is overbidden, create a 
					% new merged period that is not overbidden
					jj = jj+1;
					mergedPeriod(jj) = this.period(ii);
					sumPrice(jj) = this.price(ii).*this.period(ii);
				elseif this.price(ii)>bid && sumPrice(jj)<=bid*mergedPeriod(jj)
					% current price is overbidden, merged period is ok, create a 
					% new merged period that is overbidden
					jj = jj+1;
					mergedPeriod(jj) = this.period(ii);
					sumPrice(jj) = this.price(ii).*this.period(ii);
				end
			end
			
			mergedPeriod((jj+1):end) = [];
			sumPrice((jj+1):end) = [];
		end

		function [maxbid] = calcMaxBidFromPercentile(this, percentile, life)
			% CALCMAXBIDFROMAVGLIFE calculate the maximum bid for obtaining the given
			% percentile life
			% INPUT:
			% avglife: desired average life in hours
			% OUTPUT:
			% maxbid: maximum bid
			% avgprice: average price
			
			minbid = 0;
			maxbid = 1e+6;
			
			while maxbid-minbid > 1e-6
				bid = (maxbid+minbid)/2;
				
				[currentlife] = calcPercentileLife(this, percentile, bid);
				if currentlife < life
					minbid = bid;
				else
					maxbid = bid;
				end
			end
			
			if maxbid > 1e+6 - 1e-6
				maxbid = inf;
			end
		end		

		function [maxbid, avgprice] =  calcMaxBidFromAvgLife(this, avglife)
			% CALCMAXBIDFROMAVGLIFE calculate the maximum bid for obtaining the given
			% average life
			% INPUT:
			% avglife: desired average life in hours
			% OUTPUT:
			% maxbid: maximum bid
			% avgprice: average price
			
			minbid = 0;
			maxbid = 1e+6;
			
			while maxbid-minbid > 1e-6
				bid = (maxbid+minbid)/2;
				
				[currentlife, currentprice] = calcAverageLife(this, bid);
				if currentlife < avglife
					minbid = bid;
				else
					maxbid = bid;
					avgprice = currentprice;
				end
			end
			
			if maxbid > 1e+6 - 1e-6
				maxbid = inf;
				avgprice = nan;
			end
		end
		
		function [avglife, avgprice] = calcAverageLife(this, bid)
			% CALCAVERAGELIFE calculate the average life and price given the specified bid
			% INPUT:
			% bid: maximum offered bid		
			% OUTPUT: 
			% avglife: expected life in hours
			% avgprice: expected price
			
			[mergedPeriod, sumPrice] = this.mergePeriods(bid);
			selection = sumPrice./mergedPeriod<=bid;
			
			avglife = sum(mergedPeriod(selection).*mergedPeriod(selection)/2)/ ...
				sum(mergedPeriod);
			avgprice = sum(sumPrice(selection))/sum(mergedPeriod(selection));
		end
		
		function [percentileLife] = calcPercentileLife(this, percentile, bid)
			% CALCPERCENTILELIFE calculate the life and price given the specified bid
			% for the given percentile of requests
			% INPUT:
			% percentile: percentile of the requests expressed as a number between
			% 0.01 and 0.99
			% bid: maximum offered bid
			% OUTPUT: 
			% life: expected life in hours for the given percentile
			
			[mergedPeriod, sumPrice] = this.mergePeriods(bid);
			
			% transform each merged period into 1/100, and then round it
			precision = 1e1;
			mergedPeriod = round(mergedPeriod*precision);
			life(sum(mergedPeriod)) = 0;
			jj=0;
			
			for ii=1:length(mergedPeriod)
				% if not overbid, consider the actual life, otherwise consider 0
				if sumPrice(ii)*precision/mergedPeriod(ii) <= bid				
					life(jj + (1:mergedPeriod(ii))) = 1:mergedPeriod(ii);
				end
				jj = jj + mergedPeriod(ii);
			end
			
			life = sort(life, 'descend')/precision;
			percentileLife = life(ceil(length(life)*percentile));		
		end
		
	end
	
end

