package de.westnordost.streetcomplete.data.osmnotes;
import android.util.Log;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
import de.westnordost.streetcomplete.data.QuestStatus;
import de.westnordost.osmapi.common.SingleElementHandler;
import de.westnordost.osmapi.common.errors.OsmConflictException;
import de.westnordost.osmapi.map.MapDataDao;
import de.westnordost.osmapi.map.data.BoundingBox;
import de.westnordost.osmapi.map.data.Element;
import de.westnordost.osmapi.notes.Note;
import de.westnordost.osmapi.notes.NotesDao;
public class CreateNoteUpload
{
private static final String TAG = "CreateNoteUpload";
private final NotesDao osmDao;
private final CreateNoteDao createNoteDB;
private final NoteDao noteDB;
private final OsmNoteQuestDao noteQuestDB;
private final MapDataDao mapDataDao;
@Inject public CreateNoteUpload(
CreateNoteDao createNoteDB, NotesDao osmDao, NoteDao noteDB,
OsmNoteQuestDao noteQuestDB, MapDataDao mapDataDao)
{
this.createNoteDB = createNoteDB;
this.noteQuestDB = noteQuestDB;
this.noteDB = noteDB;
this.osmDao = osmDao;
this.mapDataDao = mapDataDao;
}
public void upload(AtomicBoolean cancelState)
{
int created = 0, obsolete = 0;
for(CreateNote createNote : createNoteDB.getAll(null))
{
if(cancelState.get()) break;
if(uploadCreateNote(createNote) != null)
{
created++;
}
else
{
obsolete++;
}
}
String logMsg = "Created " + created + " notes";
if(obsolete > 0)
{
logMsg += " but dropped " + obsolete + " because they were obsolete already";
}
Log.i(TAG, logMsg);
}
Note uploadCreateNote(CreateNote n)
{
if(isAssociatedElementDeleted(n))
{
Log.i(TAG, "Dropped to be created note " + getCreateNoteStringForLog(n) +
" because the associated element has already been deleted");
createNoteDB.delete(n.id);
return null;
}
Note newNote = createOrCommentNote(n);
if (newNote != null)
{
// add a hidden quest as a blocker so that at this location no quests are created.
// if the note was not added, don't do this (see below) -> probably based on old data
OsmNoteQuest noteQuest = new OsmNoteQuest(newNote);
noteQuest.setStatus(QuestStatus.HIDDEN);
noteDB.put(newNote);
noteQuestDB.add(noteQuest);
}
else
{
Log.i(TAG, "Dropped a to be created note " + getCreateNoteStringForLog(n) +
" because a note with the same associated element has already been closed");
// so the problem has likely been solved by another mapper
}
createNoteDB.delete(n.id);
return newNote;
}
private static String getCreateNoteStringForLog(CreateNote n)
{
return "\"" + n.text + "\" at " + n.position.getLatitude() + ", " + n.position.getLongitude();
}
private boolean isAssociatedElementDeleted(CreateNote n)
{
return n.hasAssociatedElement() && retrieveElement(n) == null;
}
private Element retrieveElement(CreateNote n)
{
switch(n.elementType)
{
case NODE: return mapDataDao.getNode(n.elementId);
case WAY: return mapDataDao.getWay(n.elementId);
case RELATION: return mapDataDao.getRelation(n.elementId);
}
return null;
}
/** Create a note at the given position, or, if there is already a note at the exact same
* position AND its associated element is the same, adds the user's message as another comment.
*
* Returns null and does not add the note comment if that note has already been closed because
* the contribution is very likely obsolete (based on old data)*/
private Note createOrCommentNote(CreateNote n)
{
if(n.hasAssociatedElement())
{
Note oldNote = findAlreadyExistingNoteWithSameAssociatedElement(n);
if(oldNote != null)
{
if(oldNote.isOpen())
{
try
{
return osmDao.comment(oldNote.id, n.text);
}
catch (OsmConflictException e)
{
return null;
}
}
else
{
return null;
}
}
}
return osmDao.create(n.position, n.text + getAssociatedElementString(n));
}
private Note findAlreadyExistingNoteWithSameAssociatedElement(final CreateNote newNote)
{
SingleElementHandler<Note> handler = new SingleElementHandler<Note>()
{
@Override public void handle(Note oldNote)
{
if(newNote.hasAssociatedElement())
{
String firstCommentText = oldNote.comments.get(0).text;
if(firstCommentText.matches(getAssociatedElementRegex(newNote)))
{
super.handle(oldNote);
}
}
}
};
final int hideClosedNoteAfter = 7;
osmDao.getAll(new BoundingBox(
newNote.position.getLatitude(), newNote.position.getLongitude(),
newNote.position.getLatitude(), newNote.position.getLongitude()
), handler, 10, hideClosedNoteAfter);
return handler.get();
}
private static String getAssociatedElementRegex(CreateNote n)
{
String elementType = n.elementType.name();
// before 0.11 - i.e. "way #123"
String oldStyleRegex = elementType+"\\s*#"+n.elementId;
// i.e. www.openstreetmap.org/way/123
String newStyleRegex = "openstreetmap\\.org\\/"+elementType+"\\/"+n.elementId;
return ".*(("+oldStyleRegex+")|("+newStyleRegex+")).*";
}
static String getAssociatedElementString(CreateNote n)
{
if(!n.hasAssociatedElement()) return "";
String elementName = n.elementType.name().toLowerCase(Locale.UK);
return "\n\nhttps://www.openstreetmap.org/" + elementName + "/" + n.elementId;
}
}