package com.planet_ink.coffee_mud.Libraries;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.DefaultPoll;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.io.IOException;
import java.util.*;

import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.XMLLibrary.XMLTag;

/*
   Copyright 2005-2022 Bo Zimmerman

   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.
*/

public class Polls extends StdLibrary implements PollManager
{
	@Override
	public String ID()
	{
		return "Polls";
	}

	public SVector<Poll> pollCache=null;

	@Override
	public boolean shutdown()
	{
		pollCache=null;
		return true;
	}

	@Override
	public void addPoll(final Poll P)
	{
		if (getCache() != null)
			getCache().add(P);
	}

	@Override
	public void removePoll(final Poll P)
	{
		if (getCache() != null)
			getCache().remove(P);
	}

	public synchronized List<Poll> getCache()
	{
		if(CMSecurity.isDisabled(CMSecurity.DisFlag.POLLCACHE))
			return null;
		if(pollCache==null)
		{
			pollCache=new SVector<Poll>();
			final List<DatabaseEngine.PollData> list=CMLib.database().DBReadPollList();
			Poll P=null;
			for(final DatabaseEngine.PollData data : list)
			{
				P=loadPollByName(data.name());
				if(P!=null)
					pollCache.addElement(P);
			}
		}
		return pollCache;
	}

	@Override
	public Poll getPoll(final String named)
	{
		final List<Poll> V=getCache();
		if(V!=null)
		{
			for(final Poll P : V)
				if(P.getName().equalsIgnoreCase(named))
					return P;
		}
		else
		{
			final Poll P=loadPollByName(named);
			return P;
		}
		return null;
	}

	@Override
	public Poll getPoll(final int x)
	{
		final Iterator<Poll> p=getPollList();
		Poll P=null;
		for(int i=0;i<=x;i++)
		{
			if(!p.hasNext())
				return null;
			P=p.next();
		}
		return P;
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<Poll>[] getMyPollTypes(final MOB mob, final boolean login)
	{
		final Iterator<Poll> i=getPollList();
		final List<Poll> list[]=new List[3];
		for(int l=0;l<3;l++)
			list[l]=new Vector<Poll>();
		for(;i.hasNext();)
		{
			final Poll P = i.next();
			if(loadPollIfNecessary(P))
			{
				if((P.mayIVote(mob))&&(login)&&(CMath.bset(P.getFlags(),Poll.FLAG_NOTATLOGIN)))
					list[1].add(P);
				else
				if(P.mayIVote(mob))
					list[0].add(P);
				else
				if(P.mayISeeResults(mob))
					list[2].add(P);
			}
		}
		return list;
	}

	@Override
	public Iterator<Poll> getPollList()
	{
		final List<Poll> L=getCache();
		if(L!=null)
			return L.iterator();
		final List<DatabaseEngine.PollData> V=CMLib.database().DBReadPollList();
		final List<Poll> list=new Vector<Poll>();
		for(final DatabaseEngine.PollData data : V)
		{
			final Poll P=(Poll)CMClass.getCommon("DefaultPoll");
			P.setName(data.name());
			P.setFlags(data.flag());
			P.setQualZapper(data.qualifyingMask());
			P.setExpiration(data.expiration());
			P.setLoaded(false);
			list.add(P);
		}
		return list.iterator();
	}

	@Override
	public void processVote(final Poll P, final MOB mob)
	{
		if(!P.mayIVote(mob))
			return;
		try
		{
			if(!loadPollIfNecessary(P))
				return;
			final StringBuffer present=new StringBuffer("");
			present.append("^O"+P.getDescription()+"^N\n\r\n\r");
			if(P.getOptions().size()==0)
			{
				mob.tell(L("@x1Oops! No options defined!",present.toString()));
				return;
			}
			Poll.PollOption PO=null;
			for(int o=0;o<P.getOptions().size();o++)
			{
				PO=P.getOptions().get(o);
				present.append("^H"+CMStrings.padLeft(""+(o+1),2)+": ^N"+PO.text+"\n\r");
			}
			if(CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN))
				present.append("^H  : ^NPress ENTER to abstain from voting.^?\n\r");

			mob.tell(present.toString());
			int choice=-1;
			while((choice<0)&&(mob.session()!=null)&&(!mob.session().isStopped()))
			{

				final String s=mob.session().prompt(L("Please make your selection (1-@x1): ",""+P.getOptions().size()));
				if((s.length()==0)&&(CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN)))
					break;
				if(CMath.isInteger(s)&&(CMath.s_int(s)>=1)&&(CMath.s_int(s)<=P.getOptions().size()))
					choice=CMath.s_int(s);
			}
			final Poll.PollResult R=new Poll.PollResult(mob.Name(),"",""+choice);
			if(CMath.bset(P.getFlags(),Poll.FLAG_VOTEBYIP))
			{
				R.ip=mob.session().getAddress();
				if((mob.playerStats()!=null)&&(mob.playerStats().getAccount()!=null))
					R.ip+="\t"+mob.playerStats().getAccount().getAccountName();
			}
			P.addVoteResult(R);
		}
		catch(final java.io.IOException x)
		{
			if(Log.isMaskedErrMsg(x.getMessage()))
				Log.errOut("Polls",x.getMessage());
			else
				Log.errOut("Polls",x);
		}
	}

	@Override
	public void modifyVote(final Poll P, final MOB mob) throws java.io.IOException
	{
		if((mob.isMonster())||(!CMSecurity.isAllowedAnywhere(mob,CMSecurity.SecFlag.POLLS)))
			return;
		boolean ok=false;
		int showFlag=-1;
		if(CMProps.getIntVar(CMProps.Int.EDITORTYPE)>0)
			showFlag=-999;
		final String oldName=P.getName();
		while(!ok)
		{
			int showNumber=0;
			String possName=CMLib.genEd().prompt(mob,P.getName(),++showNumber,showFlag,L("Name"));
			while((!possName.equalsIgnoreCase(P.getName()))&&(CMLib.polls().getPoll(possName)!=null))
				possName=possName+"!";
			P.setName(possName);
			P.setDescription(CMLib.genEd().prompt(mob,P.getDescription(),++showNumber,showFlag,L("Introduction")));
			P.setSubject(CMLib.genEd().prompt(mob,P.getSubject(),++showNumber,showFlag,L("Results Header")));
			if(P.getSubject().length()>250)
				P.setSubject(P.getSubject().substring(0,250));
			if(P.getAuthor().length()==0)
				P.setAuthor(mob.Name());
			P.setQualZapper(CMLib.genEd().prompt(mob,P.getQualZapper(),++showNumber,showFlag,L("Qual. Mask"),true));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_ACTIVE),++showNumber,showFlag,L("Poll Active")))?
				CMath.setb(P.getFlags(),Poll.FLAG_ACTIVE):CMath.unsetb(P.getFlags(),Poll.FLAG_ACTIVE));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_PREVIEWRESULTS),++showNumber,showFlag,L("Preview Results")))?
					CMath.setb(P.getFlags(),Poll.FLAG_PREVIEWRESULTS):CMath.unsetb(P.getFlags(),Poll.FLAG_PREVIEWRESULTS));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN),++showNumber,showFlag,L("Allow Abstention")))?
					CMath.setb(P.getFlags(),Poll.FLAG_ABSTAIN):CMath.unsetb(P.getFlags(),Poll.FLAG_ABSTAIN));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_VOTEBYIP),++showNumber,showFlag,L("Use IP Addresses/Accounts")))?
					CMath.setb(P.getFlags(),Poll.FLAG_VOTEBYIP):CMath.unsetb(P.getFlags(),Poll.FLAG_VOTEBYIP));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_HIDERESULTS),++showNumber,showFlag,L("Hide Results")))?
					CMath.setb(P.getFlags(),Poll.FLAG_HIDERESULTS):CMath.unsetb(P.getFlags(),Poll.FLAG_HIDERESULTS));
			P.setFlags((CMLib.genEd().prompt(mob,CMath.bset(P.getFlags(),Poll.FLAG_NOTATLOGIN),++showNumber,showFlag,L("POLL CMD only")))?
					CMath.setb(P.getFlags(),Poll.FLAG_NOTATLOGIN):CMath.unsetb(P.getFlags(),Poll.FLAG_NOTATLOGIN));
			String expirationDate="NA";
			if(P.getExpiration()>0)
				expirationDate=CMLib.time().date2String(P.getExpiration());

			expirationDate=CMLib.genEd().prompt(mob,expirationDate,++showNumber,showFlag,L("Exp. Date (MM/DD/YYYY HH:MM AP)"),true);
			if((expirationDate.trim().length()==0)||(expirationDate.equalsIgnoreCase("NA")))
				P.setExpiration(0);
			else
			{
				try
				{
					P.setExpiration(CMLib.time().string2Millis(expirationDate.trim()));
				}
				catch (final Exception e)
				{
				}
			}

			final Vector<Poll.PollOption> del=new Vector<Poll.PollOption>();
			for(int i=0;i<P.getOptions().size();i++)
			{
				final Poll.PollOption PO=P.getOptions().get(i);
				PO.text=CMLib.genEd().prompt(mob,PO.text,++showNumber,showFlag,L("Vote Option"),true);
				if(PO.text.length()==0)
					del.addElement(PO);
			}
			for(int i=0;i<del.size();i++)
				P.getOptions().remove(del.elementAt(i));

			Poll.PollOption PO=null;
			while(!mob.session().isStopped())
			{
				PO=new Poll.PollOption(
						CMLib.genEd().prompt(mob,"",++showNumber,showFlag,L("New Vote Option"),true)
				);
				if(PO.text.length()==0)
					break;
				P.getOptions().add(PO);
			}
			if(showFlag<-900)
			{
				ok=true;
				break;
			}
			if(showFlag>0)
			{
				showFlag=-1;
				continue;
			}
			showFlag=CMath.s_int(mob.session().prompt(L("Edit which? "),""));
			if(showFlag<=0)
			{
				showFlag=-1;
				ok=true;
			}
		}
		updatePoll(oldName,P);
	}

	@Override
	public void processResults(final Poll P, final MOB mob)
	{
		if(!P.mayISeeResults(mob))
			return;
		if(!loadPollIfNecessary(P))
			return;
		final StringBuffer present=new StringBuffer("");
		present.append("^O"+P.getSubject()+"^N\n\r\n\r");
		if(P.getOptions().size()==0)
		{
			mob.tell(L("@x1Oops! No options defined!",present.toString()));
			return;
		}
		int total=0;
		final int[] votes=new int[P.getOptions().size()+(CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN)?1:0)];
		Poll.PollResult R=null;
		int choice=0;
		for(int r=0;r<P.getResults().size();r++)
		{
			R=P.getResults().get(r);
			choice=CMath.s_int(R.answer);
			if(((choice<=0)&&CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN))
			||((choice>=0)&&(choice<=P.getOptions().size())))
			{
				total++;
				if(choice<=0)
					votes[votes.length-1]++;
				else
					votes[choice-1]++;
			}
		}
		Poll.PollOption O=null;
		for(int o=0;o<P.getOptions().size();o++)
		{
			O=P.getOptions().get(o);
			int pct=0;
			if(total>0)
				pct=(int)Math.round(CMath.div(votes[o],total)*100.0);
			present.append(CMStrings.padRight("^H"+(o+1),2)+": ^N"+O.text+" ^O(Votes: "+votes[o]+" - "+pct+"%)^N\n\r");
		}
		if(CMath.bset(P.getFlags(),Poll.FLAG_ABSTAIN))
		{
			int pct=0;
			if(total>0)
				pct=(int)Math.round(CMath.div(votes[votes.length-1],total)*100.0);
			present.append("    ^NAbstentions ^O("+votes[votes.length-1]+" - "+pct+"%)^N\n\r");
		}
		mob.tell(present.toString());
	}

	@Override
	public void createPoll(final Poll P)
	{
		addPoll(P);
		CMLib.database().DBCreatePoll(P.getName(),
									  P.getAuthor(),
									  P.getSubject(),
									  P.getDescription(),
									  P.getOptionsXML(),
									  (int)P.getFlags(),
									  P.getQualZapper(),
									  P.getResultsXML(),
									  P.getExpiration());
	}

	@Override
	public void updatePollResults(final Poll P)
	{
		CMLib.database().DBUpdatePollResults(P.getName(),P.getResultsXML());
	}

	@Override
	public void updatePoll(final String oldName, final Poll P)
	{
		CMLib.database().DBUpdatePoll(oldName,
									  P.getName(),
									  P.getAuthor(),
									  P.getSubject(),
									  P.getDescription(),
									  P.getOptionsXML(),
									  (int)P.getFlags(),
									  P.getQualZapper(),
									  P.getResultsXML(),
									  P.getExpiration());
	}

	@Override
	public void deletePoll(final Poll P)
	{
		removePoll(P);
		CMLib.database().DBDeletePoll(P.getName());
	}

	@Override
	public boolean loadPollIfNecessary(final Poll P)
	{
		if(P.loaded())
			return true;
		final DatabaseEngine.PollData data =CMLib.database().DBReadPoll(P.getName());
		if(data==null)
			return false;
		P.setName(data.name());
		P.setAuthor(data.authorName());
		P.setSubject(data.subject());
		P.setDescription(data.description());
		final Vector<Poll.PollOption> options=new Vector<Poll.PollOption>();
		P.setOptions(options);
		final String optionsXML=data.optionsXml();
		List<XMLLibrary.XMLTag> V2=CMLib.xml().parseAllXML(optionsXML);
		XMLTag OXV=CMLib.xml().getPieceFromPieces(V2,"OPTIONS");
		if((OXV!=null)&&(OXV.contents()!=null)&&(OXV.contents().size()>0))
		for(int v2=0;v2<OXV.contents().size();v2++)
		{
			final XMLTag XP=OXV.contents().get(v2);
			if(!XP.tag().equalsIgnoreCase("option"))
				continue;
			final Poll.PollOption PO=new Poll.PollOption(
					CMLib.xml().restoreAngleBrackets(XP.getValFromPieces("TEXT"))
			);
			options.addElement(PO);
		}
		P.setFlags(data.flag());
		P.setQualZapper(data.qualifyingMask());
		final Vector<Poll.PollResult> results=new Vector<Poll.PollResult>();
		P.setResults(results);
		final String resultsXML=data.resultsXml();
		V2=CMLib.xml().parseAllXML(resultsXML);
		OXV=CMLib.xml().getPieceFromPieces(V2,"RESULTS");
		if((OXV!=null)&&(OXV.contents()!=null)&&(OXV.contents().size()>0))
		for(int v2=0;v2<OXV.contents().size();v2++)
		{
			final XMLTag XP=OXV.contents().get(v2);
			if(!XP.tag().equalsIgnoreCase("result"))
				continue;
			final Poll.PollResult PR=new Poll.PollResult(
					XP.getValFromPieces("USER"),
					XP.getValFromPieces("IP"),
					XP.getValFromPieces("ANS"));
			results.addElement(PR);
		}
		P.setExpiration(data.expiration());
		P.setLoaded(true);
		return true;
	}

	@Override
	public Poll loadPollByName(final String name)
	{
		final Poll P=(Poll)CMClass.getCommon("DefaultPoll");
		P.setLoaded(false);
		P.setName(name);
		return loadPollIfNecessary(P)?P:null;
	}

}
