package com.iambookmaster.client.importer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import com.iambookmaster.client.beans.Paragraph;
import com.iambookmaster.client.beans.ParagraphConnection;
import com.iambookmaster.client.locale.AppConstants;
import com.iambookmaster.client.locale.AppMessages;
import com.iambookmaster.client.model.Model;
public class TextBookImporter {
private boolean paragraphNumberInSeparateLine;
private char paragraphNumberSeparator='.';
private char paragraphReferenceStart='<';
private char paragraphReferenceEnd='>';
private char secretKeyStart='(';
private char secretKeyEnd=')';
private boolean secretKeyDetection;
private TextBookImporterListener listener;
private ArrayList<Paragraph> paragraphs;
private Model model;
private boolean success;
private AppConstants appConstants;
private AppMessages appMessages;
public TextBookImporter(TextBookImporterListener listener,AppConstants appConstants,AppMessages appMessages){
this.listener = listener;
this.appConstants = appConstants;
this.appMessages = appMessages;
}
public void importBook(Model mod, String text) {
paragraphs = new ArrayList<Paragraph>();
model = mod;
listener.startStage(appConstants.importStartImporting());
success = false;
int nextPos=0;
int nextNumber=-1;
StringBuffer buffer = new StringBuffer();
boolean nextIsNumber=true;
while (true) {
int pos = text.indexOf('\n',nextPos);
String nextLine;
// if (pos==nextPos) {
// nextLine = "";
// } else if (pos>nextPos) {
if (pos>=nextPos) {
nextLine = text.substring(nextPos,pos).trim();
} else {
//last
nextLine = text.substring(nextPos).trim();
}
//check for paragraph number
if (nextIsNumber) {
//can be number
int sep = nextLine.indexOf(paragraphNumberSeparator);
if (sep<0 && paragraphNumberInSeparateLine) {
//check for just number in line
sep = nextLine.length();
}
if (sep>0) {
String nm = nextLine.substring(0,sep);
if (paragraphNumberInSeparateLine) {
nm = nextLine;
} else {
nm = nextLine.substring(0,sep);
}
int num=-1;
try {
num = Integer.parseInt(nm);
if (String.valueOf(num).equals(nm)) {
//yes!!!
} else {
//wrong
num=-1;
}
} catch (NumberFormatException e) {
//error in number
num=-1;
}
if (num>=0) {
//next paragraph number found
if (buffer.length()>0) {
Paragraph paragraph = model.addNewParagraph(null);
paragraph.setDescription(buffer.toString());
paragraph.setNumber(nextNumber);
paragraphs.add(paragraph);
}
buffer = new StringBuffer();
if (paragraphNumberInSeparateLine) {
nextIsNumber = false;
} else {
buffer.append(nextLine.substring(sep+1).trim());
}
nextNumber = num;
} else {
//just text
buffer.append('\n');
buffer.append(nextLine);
}
} else {
//just text
buffer.append('\n');
buffer.append(nextLine);
}
} else {
//just text
buffer.append(nextLine);
nextIsNumber = true;
}
if (pos<0) {
break;
} else {
nextPos = pos+1;
}
}
if (nextNumber>0) {
Paragraph paragraph = model.addNewParagraph(null);
paragraph.setDescription(buffer.toString());
paragraph.setNumber(nextNumber);
paragraphs.add(paragraph);
success = true;
listener.info(appMessages.importParagraphWasImported(paragraphs.size()));
listener.endStage(appConstants.importEndImportingParagraphs());
listener.startStage(appConstants.importStartParsingParagraphs());
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph parapraphText = paragraphs.get(i);
String desc = parapraphText.getDescription();
//try to detect name
int pos = desc.indexOf('\n');
if (pos<=0) {
parapraphText.setName(appConstants.importNoNameParagraph());
} else if (pos<30) {
parapraphText.setName(desc.substring(0,pos-1));
} else {
parapraphText.setName(desc.substring(0,30));
}
}
listener.endStage(appConstants.importEndParsingParagraphs());
} else {
listener.error((appConstants.importNoParagraphsParsed()));
}
}
public boolean isSuccess() {
return success;
}
public boolean isParagraphNumberInSeparateLine() {
return paragraphNumberInSeparateLine;
}
public void setParagraphNumberInSeparateLine(
boolean paragraphNumberInSeparateLine) {
this.paragraphNumberInSeparateLine = paragraphNumberInSeparateLine;
}
public char getParagraphNumberSeparator() {
return paragraphNumberSeparator;
}
public void setParagraphNumberSeparator(char paragraphNumberSeparator) {
this.paragraphNumberSeparator = paragraphNumberSeparator;
}
public char getParagraphReferenceEnd() {
return paragraphReferenceEnd;
}
public void setParagraphReferenceEnd(char paragraphReferenceEnd) {
this.paragraphReferenceEnd = paragraphReferenceEnd;
}
public char getParagraphReferenceStart() {
return paragraphReferenceStart;
}
public void setParagraphReferenceStart(char paragraphReferenceStart) {
this.paragraphReferenceStart = paragraphReferenceStart;
}
public ArrayList<Paragraph> getParagraphs() {
return paragraphs;
}
public void parseParagraphsText() {
//check integrity
HashMap<Integer,Paragraph> nums = new HashMap<Integer,Paragraph>(paragraphs.size());
boolean critical=false;
listener.startStage(appConstants.importStartParsingParagraphs());
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph paragraph = paragraphs.get(i);
Integer key = new Integer(paragraph.getNumber());
if (nums.containsKey(key)) {
//error
listener.error(appMessages.importDuplicateParagraphNumbers(paragraph.getNumber()));
critical=true;
} else {
nums.put(key,paragraph);
}
}
if (critical) {
listener.endStage(appMessages.importParsingTerminated());
return;
}
//parse all paragraphs
model.getParagraphConnections().clear();
for (int i = 0; i < paragraphs.size(); i++) {
Paragraph paragraph = paragraphs.get(i);
parseParagraph(paragraph,nums);
}
listener.info(appMessages.importTotalConnectionsFound(model.getParagraphConnections().size()));
//convert double connections to two-way
LinkedHashMap<Paragraph, ArrayList<ParagraphConnection>> list = new LinkedHashMap<Paragraph, ArrayList<ParagraphConnection>>(model.getParagraphs().size());
Iterator<ParagraphConnection> conns = model.getParagraphConnections().iterator();
next_con:
while (conns.hasNext()) {
ParagraphConnection connection = conns.next();
ArrayList<ParagraphConnection> curr;
if (list.containsKey(connection.getFrom())) {
curr = list.get(connection.getFrom());
} else {
//new one
curr = new ArrayList<ParagraphConnection>();
list.put(connection.getFrom(), curr);
}
for (int j = 0; j < curr.size(); j++) {
ParagraphConnection cn = curr.get(j);
if (cn.getTo()==connection.getTo()) {
//just duplicate
listener.warning(appMessages.importDuplicateConnectionRemoved(connection.getFrom().getNumber(),connection.getTo().getNumber()));
conns.remove();
continue next_con;
}
}
curr.add(connection);
curr = list.get(connection.getTo());
if (curr != null) {
//check for two way connection
for (int i = 0; i < curr.size(); i++) {
ParagraphConnection cn = curr.get(i);
if (cn.getTo()==connection.getFrom()) {
//duplicate
cn.setBothDirections(true);
listener.info(appMessages.importTwoWayConnectionFound(connection.getFrom().getNumber(),connection.getTo().getNumber()));
conns.remove();
continue next_con;
}
}
}
}
//look for start
list = null;
HashSet<Paragraph> noInput = new HashSet<Paragraph>(paragraphs.size());
noInput.addAll(paragraphs);
conns = model.getParagraphConnections().iterator();
while (conns.hasNext()) {
ParagraphConnection connection = conns.next();
noInput.remove(connection.getTo());
if (connection.isBothDirections()) {
noInput.remove(connection.getFrom());
}
}
if (noInput.size()==1) {
Paragraph paragraph = noInput.iterator().next();
if (paragraph.isFail()) {
listener.warning(appConstants.importCannotDetectStart());
} else {
listener.info(appMessages.importDetectedStartParagraph(paragraph.getNumber()));
model.makeParagraphAsStart(paragraph);
}
} else if (noInput.size()>1) {
Paragraph paragraph=null;
for (Iterator<Paragraph> iter = noInput.iterator();iter.hasNext();) {
paragraph = iter.next();
if (paragraph.isFail()==false) {
break;
}
}
if (paragraph==null) {
listener.warning(appConstants.importCannotDetectStart());
} else {
listener.warning(appConstants.importCannotDetectStartProperly());
model.makeParagraphAsStart(paragraph);
}
} else {
listener.warning((appConstants.importCannotDetectStartProperlyAllHave()));
}
listener.endStage(appConstants.importEndParsingParagraphs());
}
private void parseParagraph(Paragraph paragraph, HashMap<Integer, Paragraph> nums) {
listener.startParseParagraph(paragraph);
boolean hasOutPut=false;
if (paragraphReferenceStart==' ') {
//no marks, extract all numbers
//add 1 more character to avoid last digit problem
String descr = paragraph.getDescription()+" ";
StringBuffer buffer = new StringBuffer();
char[] text = descr.toCharArray();
int pos=-1;
int start=0;
for (int i = 0; i < text.length; i++) {
if (Character.isDigit(text[i])) {
if (pos<0) {
//start
pos = i;
}
} else if (pos>0){
//we had digits
String nm = descr.substring(pos, i);
int num = Integer.parseInt(nm);
if (num != paragraph.getNumber()) {
Integer key = num;
if (nums.containsKey(key)) {
//found link
hasOutPut = true;
Paragraph link = nums.get(key);
ParagraphConnection connection = new ParagraphConnection();
connection.setFrom(paragraph);
connection.setTo(link);
connection.setFromId(String.valueOf(paragraph.getNumber()));
connection.setToId(String.valueOf(num));
model.addParagraphConnection(connection, null);
String add = descr.substring(start, pos);
buffer.append(add);
listener.parseParagraphAddText(add);
String addDel = Model.CONNECTION_DELIMETER_FROM+descr.substring(pos, i)+Model.CONNECTION_DELIMETER_TO;
buffer.append(addDel);
listener.parseParagraphAddLink(addDel,link);
start = i;
pos = -1;
continue;
}
}
pos = -1;
}
}
String add = descr.substring(start);
buffer.append(add);
listener.parseParagraphAddText(add);
paragraph.setDescription(buffer.toString());
} else {
int pos=0;
String text = paragraph.getDescription();
StringBuffer result = null;
String start = String.valueOf(paragraphReferenceStart);
String end = String.valueOf(paragraphReferenceEnd);
while (true) {
int nextPos = text.indexOf(start,pos);
if (nextPos>=0) {
int endPos = text.indexOf(end,nextPos);
if (endPos>0) {
//remember next position
String er = text.substring(nextPos, endPos+1);
if (pos<nextPos) {
listener.parseParagraphAddText(text.substring(pos,nextPos));
}
pos = endPos+1;
if (endPos>nextPos) {
String val = text.substring(nextPos+1, endPos).trim();
try {
int num = Integer.parseInt(val);
Integer key = num;
if (nums.containsKey(key)) {
//found connection!!!
hasOutPut = true;
Paragraph link = nums.get(key);
ParagraphConnection connection = new ParagraphConnection();
connection.setFrom(paragraph);
connection.setTo(link);
connection.setFromId(String.valueOf(paragraph.getNumber()));
connection.setToId(String.valueOf(num));
model.addParagraphConnection(connection, null);
if (result==null) {
result = new StringBuffer(text);
}
result.setCharAt(nextPos, Model.CONNECTION_DELIMETER_FROM);
result.setCharAt(endPos, Model.CONNECTION_DELIMETER_TO);
listener.parseParagraphAddLink(er,link);
} else {
listener.error(appMessages.importLinkToUnknowParagraph(paragraph.getNumber(),er));
listener.parseParagraphAddError(er);
}
} catch (NumberFormatException e) {
listener.error(appMessages.importIncorrectLink(paragraph.getNumber(),er));
listener.parseParagraphAddError(er);
}
} else {
listener.error(appMessages.importEmptyLink(paragraph.getNumber(),er));
listener.parseParagraphAddError(start+" "+end);
}
} else {
listener.error(appMessages.importParagraphDoesNotHaveCloseTag(paragraph.getNumber()));
break;
}
} else {
break;
}
}
if (result != null) {
paragraph.setDescription(result.toString());
}
if (pos>0) {
listener.parseParagraphAddText(text.substring(pos));
} else {
listener.parseParagraphAddText(text);
}
}
if (hasOutPut) {
paragraph.setType(Paragraph.TYPE_NORMAL);
} else {
paragraph.setType(Paragraph.TYPE_FAIL);
}
listener.endParseParagraph(paragraph);
}
public void traseMap() {
ArrayList<Paragraph> list = model.getParagraphs();
int colMax = (int)(1+Math.round(Math.sqrt(list.size())));
int row=0;
int col=0;
for (int i = 0; i < list.size(); i++) {
Paragraph paragraph = list.get(i);
paragraph.setX(10+row*150);
paragraph.setY(10+col*40);
col++;
if (col>=colMax) {
col=0;
row++;
}
}
}
public char getSecretKeyStart() {
return secretKeyStart;
}
public void setSecretKeyStart(char secretKeyStart) {
this.secretKeyStart = secretKeyStart;
}
public char getSecretKeyEnd() {
return secretKeyEnd;
}
public void setSecretKeyEnd(char secretKeyEnd) {
this.secretKeyEnd = secretKeyEnd;
}
public boolean isSecretKeyDetection() {
return secretKeyDetection;
}
public void setSecretKeyDetection(boolean secretKeyDetection) {
this.secretKeyDetection = secretKeyDetection;
}
}