SubjectTask.java

/* ========================================================================
 * PlantUML : a free UML diagram generator
 * ========================================================================
 *
 * (C) Copyright 2009-2024, Arnaud Roques
 *
 * Project Info:  https://plantuml.com
 * 
 * If you like this project or if you find it useful, you can support us at:
 * 
 * https://plantuml.com/patreon (only 1$ per month!)
 * https://plantuml.com/paypal
 * 
 * This file is part of PlantUML.
 *
 * PlantUML 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 3 of the License, or
 * (at your option) any later version.
 *
 * PlantUML 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 *
 * Original Author:  Arnaud Roques
 * 
 *
 */
package net.sourceforge.plantuml.gantt.lang;

import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;

import com.plantuml.ubrex.UMatcher;
import com.plantuml.ubrex.builder.UBrexConcat;
import com.plantuml.ubrex.builder.UBrexLeaf;
import com.plantuml.ubrex.builder.UBrexNamed;
import com.plantuml.ubrex.builder.UBrexOneOrMore;
import com.plantuml.ubrex.builder.UBrexOptional;
import com.plantuml.ubrex.builder.UBrexOr;
import com.plantuml.ubrex.builder.UBrexPart;

import net.sourceforge.plantuml.command.CommandExecutionResult;
import net.sourceforge.plantuml.gantt.Completion;
import net.sourceforge.plantuml.gantt.DaysAsDates;
import net.sourceforge.plantuml.gantt.Failable;
import net.sourceforge.plantuml.gantt.GanttConstraint;
import net.sourceforge.plantuml.gantt.GanttDiagram;
import net.sourceforge.plantuml.gantt.core.Task;
import net.sourceforge.plantuml.gantt.core.TaskAttribute;
import net.sourceforge.plantuml.gantt.core.TaskCode;
import net.sourceforge.plantuml.gantt.core.TaskInstant;
import net.sourceforge.plantuml.gantt.ngm.Load;
import net.sourceforge.plantuml.gantt.time.TimePoint;
import net.sourceforge.plantuml.gantt.ulang.VerbPhraseAction;
import net.sourceforge.plantuml.klimt.color.HColor;
import net.sourceforge.plantuml.stereo.Stereotype;
import net.sourceforge.plantuml.stereo.StereotypePattern;
import net.sourceforge.plantuml.url.Url;

public class SubjectTask implements Subject<GanttDiagram> {

	public static final Subject<GanttDiagram> ME = new SubjectTask();
	public static final String REGEX_TASK_CODE = "\\[([^\\[\\]]+?)\\]";

	public static UBrexPart taskCode(String name) {
		return UBrexConcat.build( //
				new UBrexLeaf("["), //
				new UBrexNamed(name, new UBrexLeaf("〇+「〤[]」")), //
				new UBrexLeaf("]"));
	}

	private SubjectTask() {
	}

	@Override
	public Collection<VerbPhraseAction> getVerbPhrases() {
		final List<VerbPhraseAction> result = new ArrayList<>();
		result.add(new VerbPhraseAction(Verbs.requires, Words.uzeroOrMore(Words.ON, Words.FOR, Words.THE, Words.AT),
				new ComplementDuration()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final Load duration = (Load) complement;
				task.setLoad(duration);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.starts,
				new PairOfSomething<>(new ComplementBeforeOrAfterOrAtTaskStartOrEnd(), new ComplementWithColorLink())) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final TaskInstant when;

				final Object[] pairs = (Object[]) complement;
				when = (TaskInstant) pairs[0];
				final CenterBorderColor complement22 = (CenterBorderColor) pairs[1];

				task.setStart(when.getInstantPrecise());
				if (when.isTask()) {
					final HColor color = complement22.getCenter();
					final GanttConstraint link = new GanttConstraint(gantt.getIHtmlColorSet(),
							gantt.getCurrentStyleBuilder(), when, new TaskInstant(task, TaskAttribute.START), color);
					link.applyStyle(complement22.getStyle());
					gantt.addContraint(link);
				}
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.starts, new ComplementBeforeOrAfterOrAtTaskStartOrEnd()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final TaskInstant when;
				HColor color = null;
				when = (TaskInstant) complement;
				task.setStart(when.getInstantPrecise());
				if (when.isTask())
					gantt.addContraint(new GanttConstraint(gantt.getIHtmlColorSet(), gantt.getCurrentStyleBuilder(),
							when, new TaskInstant(task, TaskAttribute.START), color));

				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.starts, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				ComplementDate.onlyRelative()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final LocalDate start = (LocalDate) complement;

				task.setStart(TimePoint.ofStartOfDay(start));
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.starts, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				ComplementDate.any()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final LocalDate start = (LocalDate) complement;
				if (gantt.getMinDay().equals(TimePoint.epoch()))
					return CommandExecutionResult.error("No starting date for the project");

				task.setStart(TimePoint.ofStartOfDay(start));
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.isColored, new ComplementInColors()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final CenterBorderColor colors = (CenterBorderColor) complement;
				task.setColors(colors);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.isColored, Words.uexactly2(Words.FOR, Words.COMPLETION),
				new ComplementInColorsFromTo()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final CenterBorderColor[] colors = (CenterBorderColor[]) complement;
				task.setColors(colors);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.happens, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				ComplementDate.any()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				task.setLoad(Load.of(Duration.ofDays(1)));
				final LocalDate start = (LocalDate) complement;
				task.setStart(TimePoint.ofStartOfDay(start));
				task.setDiamond(true);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.happens, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				new ComplementBeforeOrAfterOrAtTaskStartOrEnd()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				task.setLoad(Load.of(Duration.ofDays(1)));
				task.setDiamond(true);
				final TaskInstant when = (TaskInstant) complement;
				if (when.getAttribute() == TaskAttribute.END)
					task.setStart(when.getInstantPrecise().decrement());
				else
					task.setStart(when.getInstantPrecise());
				return CommandExecutionResult.ok();

			}
		});

		result.add(new VerbPhraseAction(Verbs.occurs, new ComplementFromTo()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final TwoNames bothNames = (TwoNames) complement;
				final String name1 = bothNames.getName1();
				final String name2 = bothNames.getName2();
				final Task from = gantt.getExistingTask(name1);
				if (from == null)
					return CommandExecutionResult.error("No such " + name1 + " task");

				final Task to = gantt.getExistingTask(name2);
				if (to == null)
					return CommandExecutionResult.error("No such " + name2 + " task");

				task.setStart(from.getStart());
				task.setEnd(to.getEnd());
				gantt.addContraint(new GanttConstraint(gantt.getIHtmlColorSet(), gantt.getCurrentStyleBuilder(),
						new TaskInstant(from, TaskAttribute.START), new TaskInstant(task, TaskAttribute.START)));
				gantt.addContraint(new GanttConstraint(gantt.getIHtmlColorSet(), gantt.getCurrentStyleBuilder(),
						new TaskInstant(to, TaskAttribute.END), new TaskInstant(task, TaskAttribute.END)));
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.ends, new ComplementBeforeOrAfterOrAtTaskStartOrEnd()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final TaskInstant when = (TaskInstant) complement;
				task.setEnd(when.getInstantPrecise());
				gantt.addContraint(new GanttConstraint(gantt.getIHtmlColorSet(), gantt.getCurrentStyleBuilder(), when,
						new TaskInstant(task, TaskAttribute.END)));
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.ends, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				ComplementDate.onlyRelative()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final LocalDate end = (LocalDate) complement;

				task.setEnd(TimePoint.ofEndOfDayMinusOneSecond(end).increment());
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.ends, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT),
				ComplementDate.any()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final LocalDate end = (LocalDate) complement;
				if (gantt.getMinDay().equals(TimePoint.epoch()))
					return CommandExecutionResult.error("No starting date for the project");
				task.setEnd(TimePoint.ofStartOfDay(end).increment());

				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.displayOnSameRowAs, new ComplementNamed()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task1 = (Task) subject;
				final Task task2 = gantt.getExistingTask((String) complement);
				if (task2 == null)
					return CommandExecutionResult.error("No such task " + task2);

				task1.putInSameRowAs(task2);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.is, new ComplementDeleted()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				return gantt.deleteTask(task);
			}
		});

		result.add(new VerbPhraseAction(Verbs.is, new ComplementCompleted()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final Completion completed = (Completion) complement;
				task.setCompletion(completed.getCompletion());
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.pauses, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT, Words.FROM),
				new ComplementIntervals()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final DaysAsDates pauses = (DaysAsDates) complement;
				for (LocalDate day : pauses)
					task.addPause(day);

				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.pauses, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT, Words.FROM),
				new ComplementIntervalsSmart()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final DaysAsDates pauses = (DaysAsDates) complement;
				for (LocalDate day : pauses)
					task.addPause(day);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.pauses, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT, Words.FROM),
				ComplementDate.any()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final LocalDate pause = (LocalDate) complement;
				task.addPause(pause);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.pauses, Words.uzeroOrMore(Words.THE, Words.ON, Words.AT, Words.FROM),
				new ComplementDayOfWeek()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final DayOfWeek pause = (DayOfWeek) complement;
				task.addPause(pause);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.linksTo, new ComplementUrl()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final Url url = (Url) complement;
				task.setUrl(url);
				return CommandExecutionResult.ok();
			}
		});

		result.add(new VerbPhraseAction(Verbs.isDisplayedAs, new ComplementAnything()) {
			@Override
			public CommandExecutionResult execute(GanttDiagram gantt, Object subject, Object complement) {
				final Task task = (Task) subject;
				final String displayString = (String) complement;
				task.setDisplay(displayString);
				return CommandExecutionResult.ok();
			}
		});

		return result;

	}

	@Override
	public UBrexPart toUnicodeBracketedExpressionSubject() {
		return new UBrexOr( //
				new UBrexNamed("IT", Words.usingle(Words.IT)), //
				UBrexConcat.build( //
						new UBrexOptional(UBrexConcat.build( //
								new UBrexNamed("THEN", Words.usingle(Words.THEN)), //
								UBrexLeaf.spaceOneOrMore())), //
						SubjectTask.taskCode("SUBJECT"), //
						StereotypePattern.uoptional("STEREOTYPE"), //
						new UBrexOptional(UBrexConcat.build( //
								Words.uexactly(Words.AS), //
								UBrexLeaf.spaceOneOrMore(), //
								SubjectTask.taskCode("SHORTNAME"))), //
						new UBrexOptional(UBrexConcat.build( //
								Words.uexactly(Words.ON), //
								UBrexLeaf.spaceOneOrMore(), //
								new UBrexNamed("RESOURCE", new UBrexOneOrMore( //
										UBrexConcat.build( //
												new UBrexLeaf("{〇+「〤{}」}"), //
												UBrexLeaf.spaceZeroOrMore() //
										)))) //
						) //
				));
	}

	@Override
	public Failable<Task> getMe(GanttDiagram gantt, UMatcher arg) {
		final Task result;
		if (arg.get("IT", 0) != null) {
			result = gantt.getIt();
			if (result == null)
				return Failable.error("Not sure what are you refering to?");
		} else {
			final String subject = arg.get("SUBJECT", 0);
			final String shortName = arg.get("SHORTNAME", 0);
			final String then = arg.get("THEN", 0);
			final String stereotype = arg.get("STEREOTYPE", 0);

			final TaskCode code = TaskCode.fromIdAndDisplay(shortName, subject);
			result = gantt.getOrCreateTask(code, then != null);

			if (stereotype != null)
				result.setStereotype(Stereotype.build(arg.get("STEREOTYPE", 0)));

			gantt.setIt(result);
		}

		if (result == null)
			throw new IllegalStateException();

		final String resource = arg.get("RESOURCE", 0);
		if (resource != null) {
			for (final StringTokenizer st = new StringTokenizer(resource, "{}"); st.hasMoreTokens();) {
				final String part = st.nextToken().trim();
				if (part.length() > 0) {
					final boolean ok = gantt.affectResource(result, part);
					if (ok == false)
						return Failable.error("Bad argument for resource");

				}
			}

		}
		return Failable.ok(result);

	}

}