package com.iambookmaster.client.paragraph;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.iambookmaster.client.Images;
import com.iambookmaster.client.Styles;
import com.iambookmaster.client.beans.ObjectBean;
import com.iambookmaster.client.beans.Paragraph;
import com.iambookmaster.client.beans.ParagraphConnection;
import com.iambookmaster.client.common.EditorTab;
import com.iambookmaster.client.common.ExchangePanel;
import com.iambookmaster.client.common.MaskPanel;
import com.iambookmaster.client.common.ScrollContainer;
import com.iambookmaster.client.locale.AppConstants;
import com.iambookmaster.client.locale.AppLocale;
import com.iambookmaster.client.locale.AppMessages;
import com.iambookmaster.client.model.Model;
import com.iambookmaster.client.model.ParagraphListener;
import com.iambookmaster.client.paragraph.PathFinder.GameState;
import com.iambookmaster.client.paragraph.PathFinder.WayFinder.ParagraphTransition;
public class ParagraphStoryReader extends ScrollContainer implements EditorTab {
public static final int TYPE_WHOLE_STORY=0;
public static final int TYPE_ALL_SUCCESS=1;
public static final int TYPE_LONG_AND_SHORT_SUCCESS=2;
private static final String STYLE_SEPARATOR = "reader_separator";
private static final String STYLE_PARAGRAPH = "reader_paragraph";
private static final String STYLE_ERROR = "reader_error";
private final AppConstants appConstants = AppLocale.getAppConstants();
private final AppMessages appMessages = AppLocale.getAppMessages();
private boolean activated;
private Model model;
private VerticalPanel wholeStory;
private Model.FullParagraphDescriptonBuilder fullParagraphDescriptonBuilder;
private ParagraphConextMenu paragraphConextMenu;
private ParagraphListener paragraphListener;
private HashMap<Paragraph,ArrayList<ParagraphWidget>> widgets=new HashMap<Paragraph, ArrayList<ParagraphWidget>>();
private HashMap<Paragraph,ParagraphCorrectorWidget> exWidgets=new HashMap<Paragraph, ParagraphCorrectorWidget>();
public ParagraphStoryReader(Model mod) {
model = mod;
paragraphListener = new ParagraphListener() {
public void addNewParagraph(Paragraph location) {
}
public void edit(Paragraph location) {
}
public void refreshAll() {
closePanel();
}
public void remove(Paragraph location) {
closePanel();
}
public void select(Paragraph location) {
}
public void unselect(Paragraph location) {
}
public void update(Paragraph location) {
updateParagraph(location);
}
};
model.addParagraphListener(paragraphListener);
wholeStory = new VerticalPanel();
wholeStory.setSize("100%", "100%");
setScrollWidget(wholeStory);
fullParagraphDescriptonBuilder = model.getFullParagraphDescriptonBuilder();
fullParagraphDescriptonBuilder.setConnectionPattern("<img src=\""+Images.OTHER_CONNECTION+"\"/>");
fullParagraphDescriptonBuilder.setConnectionMarkedPattern("<img src=\""+Images.SELECTED_CONNECTION+"\"/>");
}
private void updateParagraph(Paragraph paragraph) {
ArrayList<ParagraphWidget> list = widgets.get(paragraph);
if (list != null) {
ArrayList<ParagraphConnection> connections = model.getOutputParagraphConnections(paragraph);
for (int i = 0; i < list.size(); i++) {
ParagraphWidget widget = list.get(i);
widget.apply(paragraph, widget.getNextParagraph(),connections);
}
}
ParagraphCorrectorWidget widget = exWidgets.get(paragraph);
if (widget != null) {
widget.apply(paragraph);
}
}
/**
* Close this panel
*/
private void closePanel() {
model.removeParagraphListener(paragraphListener);
}
public void viewAllParagraphConnectionNames() {
ArrayList<ParagraphConnection> connections = model.getParagraphConnections();
for (ParagraphConnection paragraphConnection : connections) {
if (paragraphConnection.isConditional()) {
} else {
//show from-to
}
}
}
public void externalCorrection() {
StringBuffer buffer = new StringBuffer();
wholeStory.clear();
exWidgets.clear();
final ArrayList<Paragraph> list = model.getParagraphs();
for (int i = 0; i < list.size(); i++) {
Paragraph paragraph = list.get(i);
buffer.append(i);
buffer.append('\n');
buffer.append(paragraph.getDescription());
buffer.append('\n');
buffer.append('\n');
exWidgets.put(paragraph,new ParagraphCorrectorWidget(i,paragraph));
}
ExchangePanel exchangePanel = new ExchangePanel() {
public boolean processLoad(String text) {
text = text.replace("\r\n", "\n");
StringBuilder parse = new StringBuilder(text);
//check it first
for (int i = 0; i < list.size(); i++) {
//import next paragraph
if (parse.length()==0) {
Window.alert(appMessages.modelBulkCorrectionLoadUnexpectedEnd(i));
}
int next = selectNextParagraph(parse,null);
if (next != i ) {
if (next<0) {
Window.alert(appMessages.modelBulkCorrectionLoadUnexpectedEnd(i));
} else {
Window.alert(appMessages.modelBulkCorrectionLoadWrongNumber(next,i));
}
return false;
}
}
//real import
parse.append(text);
for (int i = 0; i < list.size(); i++) {
//import next paragraph
StringBuilder builder = new StringBuilder();
selectNextParagraph(parse,builder);
Paragraph paragraph = list.get(i);
paragraph.setDescription(builder.toString());
model.updateParagraph(paragraph,null);
}
MaskPanel.hide();
return true;
}
private int selectNextParagraph(StringBuilder source, StringBuilder text) {
int number=-1;
while (true){
String line = getNextLine(source).trim();
if (isNumber(line)) {
if (number<0) {
//number must be first
number = Integer.parseInt(line);
} else {
//text, we found next paragraph
source.insert(0, '\n');
source.insert(0,line);
if (text != null) {
int l = text.length();
if (l>1 && text.substring(l-1).equals("\n")) {
text.setLength(l-1);
}
}
return number;
}
} else if (number<0) {
//number must be first
if (line.length()>0) {
//non-empty, error
return -1;
}
} else if (text != null){
//text
if (text.length()>0) {
text.append('\n');
}
text.append(line);
}
if (source.length()==0) {
return number;
}
}
}
private boolean isNumber(String line) {
for (int i = 0; i < line.length(); i++) {
if (Character.isDigit(line.charAt(i))==false) {
return false;
}
}
return line.length()>0;
}
private String getNextLine(StringBuilder source) {
int l = source.indexOf("\n");
String res;
if (l==0) {
//empty line
source.replace(0, 1, "");
res="";
} else if (l>0) {
res = source.substring(0,l);
source.replace(0, l+1, "");
return res;
} else {
res = source.toString();
source.setLength(0);
}
return res;
}
@Override
protected void onClose() {
if (Window.confirm(appConstants.modelBulkCorrection())) {
showLoad(appConstants.modelBulkCorrectionLoad());
}
}
};
exchangePanel.showExport(buffer.toString(),appConstants.modelTextForCorrection());
}
public void create(final int type) {
MaskPanel.show();
DeferredCommand.addCommand(new Command(){
public void execute() {
create(type,null);
MaskPanel.hide();
}
});
}
public void create(int type,Paragraph selectedSuccess) {
wholeStory.clear();
widgets.clear();
Paragraph start = model.getStartParagraph();
if (start==null) {
fatalError(appConstants.modelStartParagraphNotSet());
return;
}
ArrayList<ArrayList<Paragraph>> stories;
PathFinder finder = new PathFinder(model);
int findMode;
switch (type) {
case TYPE_ALL_SUCCESS:
findMode = PathFinder.FIND_ALL;
break;
case TYPE_LONG_AND_SHORT_SUCCESS:
findMode = PathFinder.FIND_MIN_MAX;
break;
default:
//TYPE_WHOLE_STORY
findMode = PathFinder.FIND_ONE; //FIND_ALL;
break;
}
if (selectedSuccess==null) {
ArrayList<Paragraph> success = model.getAllSuccessParagraphs();
if (success.size()==0) {
fatalError(appConstants.modelSuccessParagraphsNotSet());
return;
}
//collect all possible success stories
stories = new ArrayList<ArrayList<Paragraph>>();
for (int i = 0; i < success.size(); i++) {
Paragraph succes = success.get(i);
GameState objects = finder.new GameState();
ArrayList<ArrayList<Paragraph>> res = finder.findWays(start, succes, objects, null, null, findMode);
if (res != null) {
for (int j = 0; j < res.size(); j++) {
stories.add(res.get(j));
}
}
}
} else {
//show only stories to this final
GameState objects = finder.new GameState();
stories = finder.findWays(start, selectedSuccess, objects, null, null, findMode);
}
if (stories.size()==0) {
//no way to pass
fatalError(appConstants.modelSuccessParagraphsCannotBeReached());
return;
}
switch (type) {
case TYPE_ALL_SUCCESS:
for (int i = 0; i < stories.size(); i++) {
if (i>0) {
addSeparator();
}
drawStory(stories.get(i));
}
break;
case TYPE_LONG_AND_SHORT_SUCCESS:
//show longes and shortes stories
int max = 0;
int maxSize = stories.get(max).size();
int min = 0;
int minSize = stories.get(min).size();
for (int i = 1; i < stories.size(); i++) {
int cur = stories.get(i).size();
if (cur>maxSize) {
max = i;
}
if (cur<minSize) {
min = i;
}
}
if (min==max) {
//the same
drawStory(stories.get(min));
} else {
//different
drawStory(stories.get(max));
addSeparator();
drawStory(stories.get(min));
}
break;
default:
//TYPE_WHOLE_STORY
drawWholeStory(stories,finder);
break;
}
//filler
HTML html = new HTML(" ");
wholeStory.add(html);
wholeStory.setCellHeight(html,"99%");
wholeStory.setCellWidth(html,"100%");
}
private void addSeparator() {
HTML html = new HTML(" ");
html.setStyleName(STYLE_SEPARATOR);
wholeStory.add(html);
wholeStory.setCellHeight(html,"1%");
wholeStory.setCellWidth(html,"100%");
}
private void drawWholeStory(ArrayList<ArrayList<Paragraph>> stories, PathFinder finder) {
HashMap<Paragraph,ArrayList<ParagraphTransition>> map = finder.getMap();
int max = 0;
int maxSize = stories.get(max).size();
for (int i = 1; i < stories.size(); i++) {
int cur = stories.get(i).size();
if (cur>maxSize) {
max = i;
}
}
//draw the longest story
HashSet<ObjectBean> objects = new HashSet<ObjectBean>();
ArrayList<Paragraph> mainStory = stories.get(max);
stories.remove(max);
int l = mainStory.size();
for (int i = 0; i < l; i++) {
Paragraph paragraph = mainStory.get(i);
objects.addAll(paragraph.getGotObjects());
objects.removeAll(paragraph.getLostObjects());
Paragraph next;
if (i<l-1) {
next = mainStory.get(i+1);
} else {
next = null;
}
new ParagraphWidget(paragraph,next,objects,true,map);
}
mainStory = null;
//scan other stories
for (int i = 1; i < stories.size(); i++) {
ArrayList<Paragraph> story = stories.get(i);
objects.clear();
boolean chain=false;
int len = story.size()-1;
for (int j = 0; j < len; j++) {
Paragraph paragraph = story.get(j);
objects.addAll(paragraph.getGotObjects());
objects.removeAll(paragraph.getLostObjects());
boolean nextChain=false;
Paragraph next = story.get(j+1);
ArrayList<ParagraphTransition> trans = map.get(paragraph);
if (trans != null) {
for (int k = 0; k < trans.size(); k++) {
ParagraphTransition transition = trans.get(k);
if (transition.getParagraph()==next) {
//found
// new ParagraphConnectionWidget(paragraph,transition.getConnection());
// if (trans.size()==1) {
// map.remove(paragraph);
// } else {
// trans.remove(k);
// }
nextChain=true;
break;
}
}
}
if (chain) {
//chain
if (nextChain) {
//continue chain
} else {
//used, stop chain
chain=false;
new ParagraphWidget(paragraph,null,objects,false,map);
continue;
}
} else {
//no current chain
if (nextChain) {
//unused, start chan
addSeparator();
// if (j>0) {
// //draw prev. paragraph
// Paragraph prev = story.get(j-1);
// if (map.containsKey(prev)) {
// //remove this connection
// removeUserConnection(prev,paragraph,map);
// }
// new ParagraphWidget(prev,paragraph,objects,false);
// }
chain = true;
} else {
continue;
}
}
new ParagraphWidget(paragraph,next,objects,true,map);
}
}
//draw all other connections
while (map.isEmpty()==false) {
Iterator<Paragraph> iterator = map.keySet().iterator();
Paragraph paragraph = iterator.next();
ArrayList<ParagraphTransition> trans = map.get(paragraph);
if (trans==null) {
//already used
iterator.remove();
continue;
}
//unused
addSeparator();
boolean add=false;
objects.clear();
outter:
while (true) {
for (int i = 0; i < trans.size(); i++) {
ParagraphTransition transition = trans.get(i);
if (map.get(transition.getParagraph()) != null) {
//unused too, it is chain, follow it
if (transition.getConnection().getObject() != null) {
//fake this object
objects.add(transition.getConnection().getObject());
}
// new ParagraphConnectionWidget(paragraph,transition.getConnection());
new ParagraphWidget(paragraph,transition.getParagraph(),objects,true,map);
// if (trans.size()==1) {
// //the last
// map.put(paragraph,null);
// } else {
// trans.remove(i);
// }
paragraph = transition.getParagraph();
trans = map.get(paragraph);
continue outter;
}
}
//no chain or end, mark as used
// map.put(paragraph,null);
// iterator.remove();
//draw all
for (int i = 0; i < trans.size(); i++) {
ParagraphTransition transition = trans.get(i);
if (transition.getConnection().getObject() != null) {
//fake this object
objects.add(transition.getConnection().getObject());
}
if (add) {
addSeparator();
} else {
add = true;
}
// new ParagraphConnectionWidget(paragraph,transition.getConnection());
new ParagraphWidget(paragraph,transition.getParagraph(),objects,true,map);
new ParagraphWidget(transition.getParagraph(),null,objects,true,map);
}
break;
}
}
}
private void removeUserConnection(Paragraph paragraph, Paragraph next, HashMap<Paragraph, ArrayList<ParagraphTransition>> map) {
ArrayList<ParagraphTransition> trans = map.get(paragraph);
// if (trans == null) {
// System.out.println("!!!");
// }
for (int j = 0; j < trans.size(); j++) {
ParagraphTransition transition = trans.get(j);
if (transition.getParagraph()==next) {
//found, add to the panel
new ParagraphConnectionWidget(paragraph,transition.getConnection());
//remove from the list
if (trans.size()==1) {
//all connections were used
map.remove(paragraph);
} else {
//used this connection
trans.remove(j);
}
return;
}
}
}
public class ParagraphConnectionWidget extends Label implements ClickHandler{
private ParagraphConnection connection;
public ParagraphConnectionWidget(Paragraph from, ParagraphConnection connection) {
if (connection.isHiddenUsage(model.getSettings())) {
//invisible
return;
}
this.connection = connection;
setWidth("100%");
setStyleName(Styles.CLICKABLE);
addStyleName(Styles.BOLD);
addClickHandler(this);
if (connection.getFrom()==from) {
//forward
setText(connection.getNameFrom());
} else {
//back
setText(connection.getNameTo());
}
wholeStory.add(this);
wholeStory.setCellHeight(this, "1%");
wholeStory.setCellWidth(this, "100%");
}
public void onClick(ClickEvent event) {
model.selectParagraphConnection(connection, null);
}
}
/**
* Show all possible sucees stories
* @param stories
*/
private void drawStory(ArrayList<Paragraph> mainStory) {
int l = mainStory.size();
HashSet<ObjectBean> objects = new HashSet<ObjectBean>();
for (int i = 0; i < l; i++) {
Paragraph paragraph = mainStory.get(i);
objects.addAll(paragraph.getGotObjects());
objects.removeAll(paragraph.getLostObjects());
Paragraph next;
if (i<l-1) {
next = mainStory.get(i+1);
} else {
next = null;
}
new ParagraphWidget(paragraph,next,objects,true,null);
}
}
private void fatalError(String text) {
Label label = new Label(text);
label.setStyleName(STYLE_ERROR);
wholeStory.add(label);
}
public void showParagraphContextMenu(ParagraphWidget widget, Widget sender) {
if (paragraphConextMenu==null) {
paragraphConextMenu = new ParagraphConextMenu();
}
paragraphConextMenu.show(widget,sender);
}
public class ParagraphConextMenu extends PopupPanel {
private ParagraphWidget currentWidget;
private MenuBar newConnectionMenu;
private MenuItem markFinal;
private MenuItem markDraft;
private MenuItem markProposal;
public ParagraphConextMenu() {
super(true,true);
newConnectionMenu = new MenuBar(true);
newConnectionMenu.addItem(appConstants.buttonEdit(),new Command() {
public void execute() {
model.selectParagraph(currentWidget.getParagraph(), null);
model.editParagraph(currentWidget.getParagraph(), null);
hide();
}
});
markFinal = new MenuItem(appConstants.buttonMarkFinal(),new Command() {
public void execute() {
updateStatus(Model.STATUS_FINAL);
}
});
newConnectionMenu.addItem(markFinal);
markDraft = new MenuItem(appConstants.buttonMarkDraft(),new Command() {
public void execute() {
updateStatus(Model.STATUS_DRAFT);
}
});
newConnectionMenu.addItem(markDraft);
markProposal = new MenuItem(appConstants.buttonMarkProposal(),new Command() {
public void execute() {
updateStatus(Model.STATUS_PROPOSAL);
}
});
newConnectionMenu.addItem(markProposal);
add(newConnectionMenu);
}
private void updateStatus(int status) {
currentWidget.getParagraph().setStatus(status);
model.updateParagraph(currentWidget.getParagraph(), null);
hide();
}
public void show(ParagraphWidget widget, Widget sender) {
currentWidget = widget;
switch (widget.getParagraph().getStatus()) {
case Model.STATUS_DRAFT:
markDraft.setVisible(false);
markFinal.setVisible(true);
markProposal.setVisible(true);
break;
case Model.STATUS_FINAL:
markDraft.setVisible(true);
markFinal.setVisible(false);
markProposal.setVisible(true);
break;
default:
markDraft.setVisible(true);
markFinal.setVisible(true);
markProposal.setVisible(false);
//Model.STATUS_PROPOSAL:
}
setPopupPosition(sender.getAbsoluteLeft(), sender.getAbsoluteTop());
show();
}
}
public class ParagraphWidget extends HorizontalPanel implements ClickListener{
private Image image;
private HTML html;
private Paragraph paragraph;
private Paragraph nextParagraph;
private HashSet<ObjectBean> objects;
public ParagraphWidget(Paragraph par, Paragraph next, HashSet<ObjectBean> objects, boolean active, HashMap<Paragraph, ArrayList<ParagraphTransition>> map) {
paragraph = par;
nextParagraph = next;
setSize("100%", "100%");
setStyleName(STYLE_PARAGRAPH);
image = new Image();
image.addStyleName("clickable");
image.setTitle(appConstants.titleContextMenu());
image.addClickListener(this);
add(image);
setCellWidth(image, "1%");
html = new HTML();
html.setStyleName("reader_text");
html.setSize("100%", "100%");
add(html);
setCellWidth(html, "99%");
wholeStory.add(this);
wholeStory.setCellHeight(this,"1%");
wholeStory.setCellWidth(this,"100%");
ArrayList<ParagraphWidget> list = widgets.get(paragraph);
if (list==null) {
list = new ArrayList<ParagraphWidget>();
widgets.put(paragraph,list);
}
list.add(this);
this.objects = new HashSet<ObjectBean>(objects.size());
this.objects.addAll(objects);
apply(paragraph,nextParagraph,null);
if (par != null && map != null && map.containsKey(par)) {
//remove this connection
removeUserConnection(par,next,map);
}
}
public void apply(Paragraph paragraph, Paragraph nextParagraph,ArrayList<ParagraphConnection> outputConnections) {
ArrayList<String> errors = new ArrayList<String>();
fullParagraphDescriptonBuilder.setObjects(objects);
String text = fullParagraphDescriptonBuilder.getFullParagraphDescripton(paragraph, null, errors,nextParagraph,outputConnections);
String url;
if (errors.size()>0) {
//has errors
url = Images.PARAPGRAPH_ERROR;
} else if (paragraph.getStatus()==Model.STATUS_FINAL){
//no errors, final
url = Images.PARAPGRAPH_FINAL;
} else {
url = Images.PARAPGRAPH_NOT_FINAL;
}
image.setUrl(url);
html.setHTML(text);
}
public void onClick(Widget sender) {
showParagraphContextMenu(this,sender);
}
public Paragraph getParagraph() {
return paragraph;
}
public Paragraph getNextParagraph() {
return nextParagraph;
}
}
public class ParagraphCorrectorWidget extends VerticalPanel implements ClickListener{
private Paragraph paragraph;
private Label text;
public ParagraphCorrectorWidget(int number, Paragraph par) {
setSize("100%", "100%");
setStyleName(STYLE_PARAGRAPH);
Label label = new Label(String.valueOf(number));
label.addStyleName(Styles.CLICKABLE);
label.addStyleName(Styles.PARGRAPH_NUMBER);
label.addClickListener(this);
add(label);
setCellHeight(label, "1%");
setCellWidth(label, "100%");
text = new Label();
text.addStyleName(Styles.CLICKABLE);
text.addClickListener(this);
add(text);
setCellHeight(text, "99%");
setCellWidth(text, "100%");
apply(par);
wholeStory.add(this);
wholeStory.setCellHeight(this,"1%");
wholeStory.setCellWidth(this,"100%");
}
public void apply(Paragraph par) {
paragraph = par;
text.setText(par.getDescription());
}
public void onClick(Widget sender) {
model.selectParagraph(paragraph, null);
model.editParagraph(paragraph, null);
if (sender==text) {
//mark
text.addStyleName(Styles.PARGRAPH_MARKED);
} else {
text.removeStyleName(Styles.PARGRAPH_MARKED);
}
}
}
public void activate() {
if (activated==false) {
activated = true;
resetHeight();
}
}
public void close() {
model.removeParagraphListener(paragraphListener);
}
public void deactivate() {
}
}