package org.ender.wiki;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import haven.Utils;
import org.ender.wiki.Request.Callback;
import org.ender.wiki.Request.Type;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class Wiki {
private static final Pattern PAT_REDIRECT = Pattern.compile("#REDIRECT\\s*\\[\\[(.*)\\]\\]", Pattern.CASE_INSENSITIVE);
private static final Pattern PAT_CATS = Pattern.compile("\\{\\{([^\\|]*)(|.*?)}}", Pattern.MULTILINE|Pattern.DOTALL);
private static final Pattern PAT_ARGS = Pattern.compile("\\s*\\|\\s*(.*?)\\s*=\\s*([^\\|]*)", Pattern.MULTILINE|Pattern.DOTALL);
private static final String CONTENT_URL = "action=query&prop=revisions&titles=%s&rvprop=content&format=json";
private static final String SEARCH_URL = "action=query&list=search&format=json&srprop=snippet&srsearch=%s";
private static final String[] FOOD_ATTRS = new String[]{"Heals", "GluttonMin", "GluttonMax"};
private static final String[] GLUTTON_MIN = new String[]{"Min Blood", "Min Phlegm", "Min Yellow Bile", "Min Black Bile"};
private static final String[] GLUTTON_MAX = new String[]{"Max Blood", "Max Phlegm", "Max Yellow Bile", "Max Black Bile"};
static private final Map<String, String> imap = new HashMap<String, String>(15);
static public final Map<String, String> buffmap = new HashMap<String, String>(21);
static private final LinkedBlockingQueue<Request> requests = new LinkedBlockingQueue<Request>();
static private File folder;
private static long update_date;
private static final Map<String, Item> DB = new LinkedHashMap<String, Item>(9, 0.75f, true) {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<String, Item> eldest) {
return false;
}
};
public static void init(File cfg, int workers){
URL u = Wiki.class.getResource("");
String path = u.toString();
path = path.substring(10, path.indexOf('!'));
File f = new File(path);
update_date = f.lastModified();
imap.put("Arts & Crafts", "arts");
imap.put("Cloak & Dagger", "cloak");
imap.put("Faith & Wisdom", "faith");
imap.put("Flora & Fauna", "wild");
imap.put("Hammer & Nail", "nail");
imap.put("Hunting & Hideworking", "hung");
imap.put("Law & Lore", "law");
imap.put("Mines & Mountains", "mine");
imap.put("Herbs & Sprouts", "pots");
imap.put("Sparks & Embers", "fire");
imap.put("Stocks & Cultivars", "stock");
imap.put("Sugar & Spice", "spice");
imap.put("Thread & Needle", "thread");
imap.put("Natural Philosophy", "natp");
imap.put("Perennial Philosophy", "perp");
imap.put("Uses", "uses");
buffmap.put("Bread", "bread");
buffmap.put("Vegetables and Greens", "vegfood");
buffmap.put("Offal", "offal");
buffmap.put("Foraged", "forage");
buffmap.put("Poultry", "poultry");
buffmap.put("Cabbages", "cabbage");
buffmap.put("Candy", "candy");
buffmap.put("Pies", "pies");
buffmap.put("Cereal", "cereal");
buffmap.put("Meat", "meat");
buffmap.put("Cookies and Crackers", "cookies");
buffmap.put("Berries", "berry");
buffmap.put("Flowers and Herbs", "flowerfood");
buffmap.put("Seafood", "seafood");
buffmap.put("Fishes", "fish");
buffmap.put("Game", "game");
buffmap.put("Slugs Bugs and Kritters", "slugsnbugs");
buffmap.put("Nuts and Seeds", "nut");
buffmap.put("Crustacea and Shellfish", "shellfish");
buffmap.put("Pumpkins and Gourds", "pumpkin");
buffmap.put("Mushrooms", "shroom");
folder = cfg;
if(!folder.exists()){
//noinspection ResultOfMethodCallIgnored
folder.mkdirs();
}
for(int i=0; i<workers; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try{
//noinspection InfiniteLoopStatement
while(true){
try {
load(requests.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch(Exception e) {
e.printStackTrace();
System.out.println("Wiki loader thread has just died!");
}
}
}, "Wiki loader "+i);
t.setDaemon(true);
t.start();
}
}
public static Item get(String name) {
return get(name, null, Type.ITEM);
}
public static Item get(String name, Callback callback) {
return get(name, callback, Type.ITEM);
}
public static Item get(String name, Callback callback, Type type){
Item itm = null;
synchronized (DB) {
if(type == Type.ITEM){
if(DB.containsKey(name)){
itm = DB.get(name);
if(callback != null){callback.wiki_item_ready(itm);}
return itm;
}
itm = get_cache(name, true);
DB.put(name, itm);
}
}
request(new Request(name, callback, type));
return itm;
}
public static void search(String name, Callback callback){
get(name, callback, Type.SEARCH);
}
private static void request(Request request) {
try {
requests.put(request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void store(String label, Item item) {
synchronized (DB) {
DB.put(label, item);
}
//System.out.println(item.toString());
}
private static void cache(Item item) {
FileWriter fw;
try {
fw = new FileWriter(new File(folder, item.name+".xml"));
fw.write(item.toXML());
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void load(Request request){
//System.out.println(String.format("Loading '%s' at '%s'", name, Thread.currentThread().getName()));
Item item = (request.type==Type.ITEM)?get_cache(request.name, false):null;
if(item == null){
item = new Item();
item.name = request.name;
if(request.type == Type.SEARCH){
item.content = do_search(request.name);
} else {
item.content = get_content(request.name);
if(item.content != null){
String content = item.content;
parse_content(item);
item.content = content;
item.content = parse_wiki(item);
cache(item);
store(request.name, item);
}
}
}
if(request.callback != null){request.callback.wiki_item_ready(item);}
//System.out.println(String.format("Finished '%s' at '%s'", name, Thread.currentThread().getName()));
}
private static String do_search(String name) {
String content = null;
try {
URI uri = new URI("http", null, "salem-wiki.com/mediawiki", -1, "/api.php", String.format(SEARCH_URL, name), null);
URL url = uri.toURL();
String data = Utils.stream2str(url.openStream());
JSONObject json = new JSONObject(data);
JSONArray pages = json.getJSONObject("query").getJSONArray("search");
if(pages == null || pages.length() == 0){return null;}
if(pages.length() == 1){
Item item = new Item();
item.name = pages.getJSONObject(0).getString("title");
item.content = get_content(item.name);
return parse_wiki(item);
}
content = "";
for(int i=0; i<pages.length(); i++){
JSONObject page = pages.getJSONObject(i);
String title = page.getString("title");
//URI link = new URI("http", null, "salem-wiki.com/mediawiki", -1, "/index.php/"+title, null, null);
String snip = page.getString("snippet");
if(snip.length() >0){snip+="<BR/>";}
content += String.format("<B><a href=\"/index.php/%s\">%s</a></B><BR/>%s<BR/>", title, title, snip);
}
return content;
} catch (JSONException e) {
System.err.println(String.format("Error while parsing '%s':\n%s\nContent:'%s'", name, e.getMessage(), content));
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
private static void parse_content(Item item) {
Matcher m = PAT_CATS.matcher(item.content);
while(m.find()){
if(m.groupCount() == 2){
String method = m.group(1).trim();
String argsline = m.group(2);
parse(item, method, getargs(argsline));
}
item.content = m.replaceFirst("");
m = PAT_CATS.matcher(item.content);
}
}
private static void parse(Item item, String method, Map<String, String> args) {
if(method.equals("Inspirational")){
Map<String, Integer> attrs = new HashMap<String, Integer>();
for(Entry<String, String> e : args.entrySet()){
try {
String name = e.getKey();
if(name.equals("inspiration")){continue;}//skip inspiration required, as it is calculated locally
attrs.put(imap.get(name), Integer.parseInt(e.getValue()));
} catch (NumberFormatException ignored){}
}
item.attgive = attrs;
} else if(method.contains("Food")){
item_parse_food(item, args);
} else if(method.equals("Artifact")) {
String difficulty = null;
String[] profs = null;
Map<String, Integer> bonuses = new HashMap<String, Integer>();
for(Entry<String, String> entry : args.entrySet()){
String key = entry.getKey();
if(key.equals("Proficiency Type")){
profs = entry.getValue().split(", ");
} else if(key.equals("Difficulty")){
difficulty = entry.getValue();
} else {
try{bonuses.put(key, Integer.parseInt(entry.getValue()));}catch(Exception ignored){}
}
}
item.setArtifact(difficulty, profs, bonuses);
} else if(method.equals("Clothing")) {
item_parse_cloth(item, args);
}
}
private static void item_parse_food(Item item, Map<String, String> args) {
Map<String, Float[]> food = new HashMap<String, Float[]>(3);
String key = "Heals";
String arg = args.get(key);
if(arg != null) {
String[] svals = arg.split(",");
Float[] vals = new Float[4];
int i = 0;
for (String sval : svals) {
float val = 0;
try {
val = Float.parseFloat(sval);
} catch (NumberFormatException ignored) {
}
if (i > 3)
System.out.println("Higher? Oo");
vals[i] = val;
i++;
}
food.put(key, vals);
}
food.put("GluttonMax", parse_glutton(args, GLUTTON_MAX));
food.put("GluttonMin", parse_glutton(args, GLUTTON_MIN));
Map<String, Integer[]> food_reduce = new HashMap<String, Integer[]>(3);
Map<String, Integer[]> food_restore = new HashMap<String, Integer[]>(3);
for(int i = 0;i<3;i++)
{
String namerestore = args.get("FoodRestore"+(i+1));
String namereduce = args.get("FoodReduce"+(i+1));
if(namerestore.length()>0)
{
Integer[] restore = {Integer.parseInt(args.get("%Restore"+(i+1))),Integer.parseInt(args.get("%ChanceRestore"+(i+1)))};
food_restore.put(namerestore, restore);
}
if(namereduce.length()>0)
{
Integer[] reduce = {Integer.parseInt(args.get("%Reduce"+(i+1))),Integer.parseInt(args.get("%ChanceReduce"+(i+1)))};
food_reduce.put(namereduce, reduce);
}
}
item.food_restore = food_restore;
item.food_reduce = food_reduce;
try{
item.food_full = parseTime(args.get("Gluttony Time"));
} catch (NumberFormatException ignored){}
try{
item.food_uses = Integer.parseInt(args.get("Uses"));
} catch (NumberFormatException ignored){}
item.food = food;
}
private static Float[] parse_glutton(Map<String, String> args, String[] keys) {
Float[] vals = new Float[4];
for(int k=0; k<4; k++){
String key = keys[k];
String arg = args.get(key);
Float val = 0f;
if(arg != null) {
try {
val = Float.parseFloat(arg);
} catch (NumberFormatException ignored) { }
vals[k] = val;
}
}
return vals;
}
//should return the number of minutes
private static Integer parseTime(String time)
{
int mid = time.indexOf(':');
if(mid<0)
{
return 0;
}
else
{
Integer hours = Integer.parseInt(time.substring(0, mid));
Integer minutes = Integer.parseInt(time.substring(mid+1));
return hours*60+minutes;
}
}
private static void item_parse_cloth(Item item, Map<String, String> args) {
if(args == null){ return; }
int slots = 0;
String sslots = args.containsKey("Artificer Slots")?args.get("Artificer Slots"):args.get("slots");
try{slots = Integer.parseInt(sslots);}catch(Exception ignored){}
item.setClothing(slots);
}
private static Map<String, String> getargs(String argsline) {
Map<String, String> args = new HashMap<String, String>();
Matcher m = PAT_ARGS.matcher(argsline);
while(m.find()){
if(m.groupCount() == 2){
String name = m.group(1).trim();
String val = m.group(2).trim();
args.put(name, val);
}
}
return args;
}
private static String get_content(String name){
String content;
String data = null;
try {
URI uri = new URI("http", null, "www.salem-wiki.com/mediawiki", -1, "/api.php", null, null);
URL link = uri.toURL();
HttpURLConnection conn = (HttpURLConnection) link.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
data = String.format(CONTENT_URL, URLEncoder.encode(name, "UTF-8"));
DataOutputStream wr = new DataOutputStream(conn.getOutputStream ());
wr.writeBytes(data);
wr.flush();
wr.close();
data = Utils.stream2str(conn.getInputStream());
JSONObject json = new JSONObject(data);
json = json.getJSONObject("query").getJSONObject("pages");
String pageid = JSONObject.getNames(json)[0];
content = json.getJSONObject(pageid).getJSONArray("revisions").getJSONObject(0).getString("*");
String redirect = get_redirect(content);
if(redirect != null){
return get_content(redirect);
}
return content;
} catch (JSONException e) {
System.err.println(String.format("Error while parsing '%s':\n%s\nData:'%s'", name, e.getMessage(), data));
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
private static String parse_wiki(Item item){
String content;
try {
URI uri = new URI("http", null, "salem-wiki.com/mediawiki", -1, "/api.php", null, null);
URL link = uri.toURL();
HttpURLConnection conn = (HttpURLConnection) link.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
String data = URLEncoder.encode("__NOTOC__\n"+item.content.trim(), "UTF-8");
String title = URLEncoder.encode(item.name, "UTF-8");
String req = String.format("action=parse&format=json&text=%s&title=%s", data, title);
DataOutputStream wr = new DataOutputStream(conn.getOutputStream ());
wr.writeBytes(req);
wr.flush();
wr.close();
data = Utils.stream2str(conn.getInputStream());
JSONObject json = new JSONObject(data);
json = json.getJSONObject("parse").getJSONObject("text");
content = json.getString("*");
return content;
} catch (JSONException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return null;
}
private static String get_redirect(String content) {
Matcher m = PAT_REDIRECT.matcher(content);
if(m.find() && m.groupCount() == 1){
return m.group(1);
}
return null;
}
private static Item get_cache(String name, boolean fast) {
File f = new File(folder, name+".xml");
if(!f.exists()){return null;}
if(!fast && has_update(name, f.lastModified())){
return null;
}
return load_cache(f);
}
private static Item load_cache(File f) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(f);
Item item = new Item();
item.name = doc.getDocumentElement().getAttribute("name");
item.required = parse_cache(doc, "required");
item.locations = parse_cache(doc, "locations");
item.reqby = parse_cache(doc, "reqby");
item.tech = parse_cache(doc, "tech");
item.unlocks = parse_cache(doc, "unlocks");
item.attreq = parse_cache_map(doc, "attreq");
item.attgive = parse_cache_map(doc, "attgive");
parse_cache_food(doc, item);
item.content = parse_cache_content(doc, "content");
item_parse_cloth(item, parse_cache_str_map(doc, "cloth"));
item_parse_artifact(item, doc);
return item;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
return null;
}
private static void item_parse_artifact(Item item, Document doc) {
String tag = "artifact";
NodeList list = doc.getElementsByTagName(tag);
if(list.getLength() > 0){
Node node = list.item(0);
NamedNodeMap attributes = node.getAttributes();
String difficulty = attributes.getNamedItem("difficulty").getNodeValue();
String[] profs = attributes.getNamedItem("profs").getNodeValue().split(", ");
String[] bonuses = attributes.getNamedItem("bonuses").getNodeValue().split(", ");
Map<String, Integer> art_bonuses = new HashMap<String, Integer>(bonuses.length);
for(String bonus : bonuses){
String[] entry = bonus.split("=");
try{art_bonuses.put(entry[0], Integer.parseInt(entry[1]));}catch(Exception ignored){}
}
item.setArtifact(difficulty, profs, art_bonuses);
}
}
private static String parse_cache_content(Document doc, String tag) {
NodeList list = doc.getElementsByTagName(tag);
if(list.getLength() > 0){
Node item = list.item(0);
return item.getTextContent();
}
return null;
}
private static void parse_cache_food(Document doc, Item item) {
NodeList list = doc.getElementsByTagName("food");
if(list.getLength() > 0){
Node node = list.item(0);
Map<String, Float[]> food = new HashMap<String, Float[]>();
NamedNodeMap attrs = node.getAttributes();
for(String name : FOOD_ATTRS){
Node attr = attrs.getNamedItem(name);
if(attr != null && attr.getNodeValue()!=null)
{
String svals[] = attr.getNodeValue().split(" ");
Float[] vals = new Float[4];
for(int j=0; j<4; j++){
try{
vals[j] = Float.parseFloat(svals[j]);
}catch (NumberFormatException ex){
vals[j] = 0.0f;
}
}
food.put(name, vals);
}
}
item.food = food;
Map<String, Integer[]> food_reduce = new HashMap<String, Integer[]>(3);
Map<String, Integer[]> food_restore = new HashMap<String, Integer[]>(3);
for(int i = 0;i<3;i++)
{
String namerestore = attrs.getNamedItem("FoodRestore"+(i+1)).getNodeValue();
Integer[] restore = {Integer.parseInt(attrs.getNamedItem("%Restore"+(i+1)).getNodeValue()),Integer.parseInt(attrs.getNamedItem("%ChanceRestore"+(i+1)).getNodeValue())};
String namereduce = attrs.getNamedItem("FoodReduce"+(i+1)).getNodeValue();
Integer[] reduce = {Integer.parseInt(attrs.getNamedItem("%Reduce"+(i+1)).getNodeValue()),Integer.parseInt(attrs.getNamedItem("%ChanceReduce"+(i+1)).getNodeValue())};
if(namerestore.length()>0)
food_restore.put(namerestore, restore);
if(namereduce.length()>0)
food_reduce.put(namereduce, reduce);
}
item.food_restore = food_restore;
item.food_reduce = food_reduce;
try{
item.food_full = parseTime(attrs.getNamedItem("full").getNodeValue());
} catch (NumberFormatException ignored){}
try{
item.food_uses = Integer.parseInt(attrs.getNamedItem("uses").getNodeValue());
} catch (NumberFormatException ignored){}
}
}
private static Set<String> parse_cache(Document doc, String tag) {
NodeList list = doc.getElementsByTagName(tag);
if(list.getLength() > 0){
Set<String> items = new HashSet<String>(list.getLength());
for(int i=0; i< list.getLength(); i++){
items.add(list.item(i).getAttributes().getNamedItem("name").getNodeValue());
}
return items;
}
return null;
}
private static Map<String, String> parse_cache_str_map(Document doc, String tag) {
NodeList list = doc.getElementsByTagName(tag);
if(list.getLength() > 0){
Node item = list.item(0);
Map<String, String> items = new HashMap<String, String>();
NamedNodeMap attrs = item.getAttributes();
for(int i=0; i< attrs.getLength(); i++){
Node attr = attrs.item(i);
items.put(attr.getNodeName(), attr.getNodeValue());
}
return items;
}
return null;
}
private static Map<String, Integer> parse_cache_map(Document doc, String tag) throws NullPointerException{
NodeList list = doc.getElementsByTagName(tag);
if(list.getLength() > 0){
Node item = list.item(0);
Map<String, Integer> items = new HashMap<String, Integer>();
NamedNodeMap attrs = item.getAttributes();
for(int i=0; i< attrs.getLength(); i++){
Node attr = attrs.item(i);
String name = attr.getNodeName();
Integer value = Integer.decode(attr.getNodeValue());
if(name.equalsIgnoreCase("null")){
throw new NullPointerException("WIKI: argument name is null!");
}
items.put(name, value);
}
return items;
}
return null;
}
private static boolean has_update(String name, long date) {
try {
if(date < update_date){return true;}//ignore old cache
//String p = String.format("%s%s", WIKI_URL, name);
URI uri = new URI("http","salem-wiki.com/mediawiki","/index.php/"+name, null);
URL url = uri.toURL();
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("HEAD");
conn.setIfModifiedSince(date);
//conn.disconnect();
if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){
return true;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (URISyntaxException e1) {
e1.printStackTrace();
}
return false;
}
}