%SEALEVELMODEL class definition
%
%   Usage:
%      slm = sealevelmodel(varargin)
%
%      where varargin is a variable list of options:
%
%   Example:
%      slm = sealevel('icecap',md_greenland,'icecap',md_antarctica,'earth',md_earth);

classdef sealevelmodel < handle
	properties (SetAccess=public) %Model fields
		% {{{
		icecaps          = {}; % list of land/ice models, name should  be change longer term.
		earth            = 0;  % model for the whole earth
		basins           = {}; % list of basins, matching icecaps, where shapefile info is held
		cluster          = 0;
		miscellaneous    = 0;
		settings         = 0;
		private          = 0;
		mergedcaps       = 0;
		transitions      = {};
		eltransitions    = {};
		planet           = '';
		%}}}
	end
	methods
		function slm = sealevelmodel(varargin) % {{{
			slm=setdefaultparameters(slm);

			if nargin==1,

				options=pairoptions(varargin{:});

				%recover all the icecap models:
				slm.icecaps=getfieldvalues(options,'ice_cap',{});
				
				%recover the earth model:
				slm.earth = getfieldvalue(options,'earth',0);

				%set planet type:
				slm.planet=getfieldvalue(options,'planet','earth');

			end
		end
		%}}}
		function checkconsistency(slm,solutiontype) % {{{

			%is the coupler turned on?
			for i=1:length(slm.icecaps),
				if slm.icecaps{i}.transient.iscoupler==0,
					warning(sprintf('sealevelmodel.m::checkconsistency: icecap model %s should have the transient coupler option turned on!',slm.icecaps{i}.miscellaneous.name));
				end
			end
				
			if slm.earth.transient.iscoupler==0,
				warning('sealevelmodel.m::checkconsistency: earth model should have the transient coupler option turned on!');
			end

			%check that the transition vectors have the right size:
			for i=1:length(slm.icecaps),
				if slm.icecaps{i}.mesh.numberofvertices ~= length(slm.earth.solidearth.transitions{i}),
					error(['sealevelmodel.m::checkconsistency: issue with size of transition vector for ice cap: ' num2str(i) ' name: ' slm.icecaps{i}.miscellaneous.name]);
				end
			end
			
			%check that runfrequency is the same everywhere:
			for i=1:length(slm.icecaps),
				if slm.icecaps{i}.solidearth.settings.runfrequency~=slm.earth.solidearth.settings.runfrequency,
					error(sprintf('sealevelmodel.m::checkconsistency: icecap model %s should have the same run frequency as earth!',slm.icecaps{i}.miscellaneous.name));
				end
			end

			%make sure steric_rate is the same everywhere:
			for i=1:length(slm.icecaps),
				md= slm.icecaps{i};
				if ~isempty(find(md.dsl.steric_rate - slm.earth.dsl.steric_rate(slm.earth.dsl.transitions{i}))),
					error(sprintf('sealevelmodel.m::checkconsistency: steric rate on ice cap %s is not the same as for the earth\n',md.miscellaneous.name));
				end
			end

			%make sure grd is the same everywhere:
			for i=1:length(slm.icecaps),
				md= slm.icecaps{i};
				if md.solidearthsettings.isgrd~=slm.earth.solidearthsettings.isgrd
					error(sprintf('sealevelmodel.m::checkconsistency: isgrd on ice cap %s is not the same as for the earth\n',md.miscellaneous.name));
				end
			end

			%make sure that there is no solid earth external forcing on the basins:
			for i=1:length(slm.icecaps),
				md= slm.icecaps{i};
				if ~isempty(md.solidearth.external),
					error('sealevelmodel.m::checkconsistency: cannot run external forcings on an ice sheet when running a coupling earth/ice sheet model');
				end

			end
			%make sure that we have the right grd model for computing out sealevel patterns:
			for i=1:length(slm.icecaps),
				md= slm.icecaps{i};
				if md.solidearth.settings.grdmodel~=0
					error(sprintf('sealevelmodel.m::checkconsistency: ice sheets do not run GRD module, specify solidearth.settings.grdmodel=0 on ice cap %i',i));
				end
			end

		end
		%}}}
		function slm = setdefaultparameters(slm) % {{{

			%initialize subclasses
			slm.icecaps           = {};
			slm.earth             = {};
			slm.miscellaneous     = miscellaneous();
			slm.settings          = issmsettings();
			slm.private           = private();
			slm.cluster           = generic();
			slm.transitions       = {};
			slm.eltransitions     = {};
			slm.planet            = 'earth';
		end
		%}}}
		function disp(self) % {{{
			disp(sprintf('%19s: %-22s -- %s','icecaps'         ,['[' num2str(length(self.icecaps)) 'x1 ' class(self.icecaps) ']'],'ice caps'));
			disp(sprintf('%19s: %-22s -- %s','earth'           ,['[1x1 ' class(self.earth) ']'],'earth'));
			disp(sprintf('%19s: %-22s -- %s','settings'        ,['[1x1 ' class(self.settings) ']'],'settings properties'));
			disp(sprintf('%19s: %-22s -- %s','cluster'         ,['[1x1 ' class(self.cluster) ']'],'cluster parameters (number of cpus...)'));
			disp(sprintf('%19s: %-22s -- %s','miscellaneous'   ,['[1x1 ' class(self.miscellaneous) ']'],'miscellaneous fields'));
		end % }}}
		function self=mergeresults(self) % {{{
			champs=fieldnames(self.icecaps{1}.results.TransientSolution);
			for i=1:length(self.mergedcaps)/2,
				md=self.mergedcaps{2*(i-1)+1}; trans=self.mergedcaps{2*(i-1)+2};
				%icecaps=self.icecaps(self.range{2*(i-1)+2});
				for j=1:length(self.icecaps{1}.results.TransientSolution),
					for k=1:length(champs),
						if strcmpi(class(icecaps{1}.results.TransientSolution(j).(champs{k})),'double'),
							%vertex or element?
							if length(icecaps{1}.results.TransientSolution(j).(champs{k}))==icecaps{1}.mesh.numberofvertices,
								md.results.TransientSolution(j).(champs{k})=zeros(md.mesh.numberofvertices,1);
								for l=1:length(trans),
									resultcap=icecaps{l}.results.TransientSolution(j).(champs{k});
									md.results.TransientSolution(j).(champs{k})(trans{l})=resultcap;
								end
							else
								if strcmpi(champs{k},'IceVolume') | strcmpi(champs{k},'IceVolumeAboveFloatation') ,
									md.results.TransientSolution(j).(champs{k})=0;
									for l=1:length(trans),
										resultcap=icecaps{l}.results.TransientSolution(j).(champs{k});
										md.results.TransientSolution(j).(champs{k})= md.results.TransientSolution(j).(champs{k})+resultcap;
									end
								elseif strcmpi(champs{k},'time'),
									md.results.TransientSolution(j).(champs{k})= icecaps{1}.results.TransientSolution(j).(champs{k});
								else
									continue;
								end
							end
						else
							continue;
						end
					end
				end
				self.mergedcaps{2*(i-1)+1}=md;
			end
		end % }}}
		function listcaps(self) % {{{
			for  i=1:length(self.icecaps),
				disp(sprintf('%i: %s',i,self.icecaps{i}.miscellaneous.name));
			end
		end % }}}
		function n=ncaps(self) % {{{
			n=length(self.icecaps);
		end % }}}
		function list=continents(self) % {{{
			list={};
			for  i=1:length(self.basins),
				list{end+1}=self.basins{i}.continent;
			end
			list=unique(list);
		end % }}}
		function list=basinsfromcontinent(self,continent) % {{{
			list={};
			for  i=1:length(self.icecaps),
				if strcmpi(self.basins{i}.continent,continent),
					list{end+1}=self.basins{i}.name;
				end
			end
			list=unique(list);
		end % }}}
		function addbasin(self,bas) % {{{
			if ~strcmpi(class(bas),'basin')
				error('addbasin method only takes a ''basin'' class object as input');
			end;
			self.basins{end+1}=bas;
		end % }}}
		function intersections2d(self,varargin) % {{{

			options=pairoptions(varargin{:});
			force=getfieldvalue(options,'force',0);
			
			%initialize, to avoid issues of having more transitions than meshes.
			self.transitions={};
			self.eltransitions={};

			%for elements:
			xe=self.earth.mesh.x(self.earth.mesh.elements)*[1;1;1]/3;
			ye=self.earth.mesh.y(self.earth.mesh.elements)*[1;1;1]/3;
			
			for i=1:length(self.icecaps),
				mdi=self.icecaps{i};
		
				%for elements:
				xei=mdi.mesh.x(mdi.mesh.elements)*[1;1;1]/3;
				yei=mdi.mesh.y(mdi.mesh.elements)*[1;1;1]/3;
		
				disp(sprintf('Computing vertex intersections for basin %s',self.basins{i}.name));
			
				self.transitions{end+1}=meshintersect2d(self.earth.mesh.x,self.earth.mesh.y,mdi.mesh.x,mdi.mesh.y,'force',force);

				self.eltransitions{end+1}=meshintersect2d(xe,ye,xei,yei,'force',force);
			end
		end % }}}
		function intersections(self,varargin) % {{{

			options=pairoptions(varargin{:});
			force=getfieldvalue(options,'force',0);
			
			%initialize, to avoid issues of having more transitions than meshes.
			self.transitions={};
			self.eltransitions={};

			%for elements:
			xe=self.earth.mesh.x(self.earth.mesh.elements)*[1;1;1]/3;
			ye=self.earth.mesh.y(self.earth.mesh.elements)*[1;1;1]/3;
			ze=self.earth.mesh.z(self.earth.mesh.elements)*[1;1;1]/3;
			
			for i=1:length(self.icecaps),
				mdi=self.icecaps{i};
				mdi=TwoDToThreeD(mdi,self.planet);
		
				%for elements:
				xei=mdi.mesh.x(mdi.mesh.elements)*[1;1;1]/3;
				yei=mdi.mesh.y(mdi.mesh.elements)*[1;1;1]/3;
				zei=mdi.mesh.z(mdi.mesh.elements)*[1;1;1]/3;
		
				disp(sprintf('Computing vertex intersections for basin %s',self.basins{i}.name));
			
				self.transitions{end+1}=meshintersect3d(self.earth.mesh.x,self.earth.mesh.y,self.earth.mesh.z,mdi.mesh.x,mdi.mesh.y,mdi.mesh.z,'force',force);

				self.eltransitions{end+1}=meshintersect3d(xe,ye,ze,xei,yei,zei,'force',force);
			end
		end % }}}
		function checkintersections(self) % {{{
			flags=zeros(self.earth.mesh.numberofvertices,1);
			for i=1:length(self.basins),
				flags(self.transitions{i})=i;
			end
			plotmodel(self.earth,'data',flags,'coastline','on');

		end % }}}
		function checkbasinconsistency(self) % {{{
			for i=1:length(self.basins),
				self.basins{i}.checkconsistency();
			end

		end % }}}
		function baslist=basinindx(self,varargin) % {{{
			options=pairoptions(varargin{:});
			continent=getfieldvalue(options,'continent','all');
			bas=getfieldvalue(options,'basin','all');

			%expand continent list: {{{
			if iscell(continent),
				if length(continent)==1,
					if strcmpi(continent{1},'all'),
						%need to transform this into a list of continents:
						continent={};
						for i=1:length(self.basins),
							continent{end+1}=self.basins{i}.continent;
						end
						continent=unique(continent);
					else
						%nothing to do: assume we have a list of continents
					end
				else
					%nothing to do: assume we have a list of continents
				end
			else
				if strcmpi(continent,'all'),
					%need to transform this into a list of continents:
					continent={};
					for i=1:length(self.basins),
						continent{end+1}=self.basins{i}.continent;
					end
					continent=unique(continent);
				else
					continent={continent};
				end
			end
			%}}}
			%expand basins list using the continent list above and the extra bas discriminator: %{{{
			if iscell(bas),
				if length(bas)==1,
					if strcmpi(bas{1},'all'),
						%need to transform this into a list of basins:
						baslist=[];
						for i=1:length(self.basins),
							if self.basins{i}.iscontinentany(continent{:}),
								baslist(end+1)=i;
							end
						end
						baslist=unique(baslist);
					else
					bas=bas{1};
					baslist=[];
					for i=1:length(self.basins),
						if self.basins{i}.iscontinentany(continent{:}),
							if self.basins{i}.isnameany(bas),
								baslist(end+1)=i;
							end
						end
					end

					end
				else
					%we have a list of basin names:
					baslist=[];
					for i=1:length(bas),
						basname=bas{i};
						for j=1:length(self.basins),
							if self.basins{j}.iscontinentany(continent{:}),
								if self.basins{j}.isnameany(basname),
									baslist(end+1)=j;
								end
							end
						end
						baslist=unique(baslist);
					end
				end
			else
				if strcmpi(bas,'all'),
					baslist=[];
					for i=1:length(self.basins),
						if self.basins{i}.iscontinentany(continent{:}),
							baslist(end+1)=i;
						end
					end
					baslist=unique(baslist);
				else
					baslist=[];
					for i=1:length(self.basins),
						if self.basins{i}.iscontinentany(continent{:}),
							if self.basins{i}.isnameany(bas),
								baslist(end+1)=i;
							end
						end
					end
					baslist=unique(baslist);
				end
			end
			%}}}

		end % }}}
		function addicecap(self,md) % {{{
			if ~strcmpi(class(md),'model')
				error('addicecap method only takes a ''model'' class object as input');
			end
			self.icecaps{end+1}=md;
		end % }}}
		function basinsplot3d(self,varargin) % {{{
			for i=1:length(self.basins),
				self.basins{i}.plot3d(varargin{:});
			end
		end % }}}
		function caticecaps(self,varargin) % {{{
			
			%recover options:
			options=pairoptions(varargin{:});
			tolerance=getfieldvalue(options,'tolerance',.65);
			loneedgesdetect=getfieldvalue(options,'loneedgesdetect',0);
	
			%make 3D model:
			models=self.icecaps;
			for i=1:length(models),
				models{i}=TwoDToThreeD(models{i},self.planet);
			end
			
			%Plug all models together:
			md=models{1};
			for i=2:length(models),
				md=modelmerge3d(md,models{i},'tolerance',tolerance);
				md.private.bamg.landmask=[md.private.bamg.landmask;models{i}.private.bamg.landmask];
			end

			%Look for lone edges if asked for it: {{{
			if loneedgesdetect,
				edges=loneedges(md);
				plotmodel(md,'data',md.mask.land_levelset);
				hold on;
				for i=1:length(edges),
					ind1=edges(i,1);
					ind2=edges(i,2);
					%plot([md.mesh.x(ind1),md.mesh.x(ind2)],[md.mesh.y(ind1),md.mesh.y(ind2)],'r*-');
					plot3([md.mesh.x(ind1),md.mesh.x(ind2)],[md.mesh.y(ind1),md.mesh.y(ind2)],[md.mesh.z(ind1),md.mesh.z(ind2)],'g*-');
				end
			end %}}}
	
			%Plug into earth:
			self.earth=md;

			%Create mesh radius:
			self.earth.mesh.r=planetradius('earth')*ones(md.mesh.numberofvertices,1);

		end % }}}
		function caticecaps2d(self,varargin) % {{{

			%recover options:
			options=pairoptions(varargin{:});
			tolerance=getfieldvalue(options,'tolerance',1e-5);
			loneedgesdetect=getfieldvalue(options,'loneedgesdetect',0);
			models=self.icecaps;

			%Plug all models together:
			md=models{1};
			for i=2:length(models),
				md=modelmerge2d(md,models{i},'tolerance',tolerance);
			end

			%Look for lone edges if asked for it: {{{
			if loneedgesdetect,
				edges=loneedges(md);
				hold on;
				for i=1:length(edges),
					ind1=edges(i,1);
					ind2=edges(i,2);
					plot([md.mesh.x(ind1),md.mesh.x(ind2)],[md.mesh.y(ind1),md.mesh.y(ind2)],'g*-');
				end
			end %}}}

			%Plug into earth:
			self.earth=md;

		end % }}}
		function viscousiterations(self) % {{{
			for  i=1:length(self.icecaps),
				ic=self.icecaps{i};
				mvi=ic.results.TransientSolution(1).StressbalanceConvergenceNumSteps;
				for j=2:length(ic.results.TransientSolution)-1,
					mvi=max(mvi,ic.results.TransientSolution(j).StressbalanceConvergenceNumSteps);
				end
				disp(sprintf('%i, %s: %i',i,self.icecaps{i}.miscellaneous.name,mvi));
			end
		end % }}}
		function maxtimestep(self) % {{{
			for  i=1:length(self.icecaps),
				ic=self.icecaps{i};
				mvi=length(ic.results.TransientSolution);
				timei=ic.results.TransientSolution(end).time;
				disp(sprintf('%i, %s: %i/%g',i,self.icecaps{i}.miscellaneous.name,mvi,timei));
			end
			mvi=length(self.earth.results.TransientSolution);
			timei=self.earth.results.TransientSolution(end).time;
			disp(sprintf('Earth: %i/%g',mvi,timei));
		end % }}}
		function transfer(self,string) % {{{
			%Recover field size in one icecap:
			eval(['n=size(self.icecaps{1}.' string ',1);']);
			if n==self.icecaps{1}.mesh.numberofvertices,
				eval(['self.earth.' string '=zeros(self.earth.mesh.numberofvertices,1);']);
				for i=1:length(self.icecaps),
					eval(['self.earth.' string '(self.transitions{' num2str(i) '})=self.icecaps{' num2str(i) '}.' string ';']);
				end
			elseif n==(self.icecaps{1}.mesh.numberofvertices+1),
				%dealing with a transient dataset.
				%check that all timetags are similar between all icecaps:  %{{{
				for i=1:length(self.icecaps),
					eval(['capfieldi= self.icecaps{' num2str(i) '}.' string ';']);
					for j=(i+1):length(self.icecaps),
						eval(['capfieldj= self.icecaps{' num2str(j) '}.' string ';']);
						if ~isequal(capfieldi(end,:),capfieldj(end,:)),
							error(['Time stamps for ' string ' field are different between icecaps ' num2str(i) ' and ' num2str(j)]);
						end
					end
				end
				eval(['capfield1= self.icecaps{1}.' string ';']);
				times=capfield1(end,:);
				nsteps=length(times);
				%}}}
				%initialize:  %{{{
				eval(['field=zeros(self.earth.mesh.numberofvertices+1,' num2str(nsteps) ');']);
				eval(['field(end,:)=times;']); %transfer the times only, not the values
				%}}}
				%transfer all time fields: {{{
				for i=1:length(self.icecaps),
					eval(['capfieldi= self.icecaps{' num2str(i) '}.' string ';']);
					for j=1:nsteps,
						eval(['field(self.transitions{' num2str(i) '},' num2str(j) ')=capfieldi(1:end-1,' num2str(j) ');']); %transfer only the values, not the time.
					end
				end
				eval(['self.earth.' string '=field;']); %do not forget to plug the field variable into its final location
				%}}}
			elseif n==self.icecaps{1}.mesh.numberofelements,
				eval(['self.earth.' string '=zeros(self.earth.mesh.numberofelements,1);']);
				for i=1:length(self.icecaps),
					eval(['self.earth.' string '(self.eltransitions{' num2str(i) '})=self.icecaps{' num2str(i) '}.' string ';']);
				end
			else
				error('not supported yet');
			end
		end % }}}
		function self=homogeneize(self,noearth) % {{{
			if nargin==1,
				noearth=0;
			end
			mintimestep=Inf;
			for  i=1:length(self.icecaps),
				ic=self.icecaps{i};
				mintimestep=min(mintimestep, length(ic.results.TransientSolution));
			end
			if ~noearth,
				mintimestep=min(mintimestep, length(self.earth.results.TransientSolution));
			end
			
			for  i=1:length(self.icecaps),
				ic=self.icecaps{i};
				ic.results.TransientSolution=ic.results.TransientSolution(1:mintimestep);
				self.icecaps{i}=ic;
			end
			ic=self.earth;
			if ~noearth,
				ic.results.TransientSolution=ic.results.TransientSolution(1:mintimestep);
			end
			self.earth=ic;
		end % }}}
		function self=initializemodels(self) % {{{

			for i=1:length(self.basins),
				md=model();
				md.miscellaneous.name=self.basins{i}.name;
				self.addicecap(md);
			end
		end % }}}
	end
end
