package semantics;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.HashMap;
import java.util.HashSet;
import java.util.logging.Logger;
import java.util.Set;
import semantics.err.CommunicationException;
import semantics.err.IncompatibleIndividualException;
import semantics.err.MessageException;
import semantics.err.SemanticCastException;
import semantics.model.Conceptual;
import semantics.model.Individual;
import semantics.model.Roleish;


public class KnowBase {
  public static final String DEFAULT_HOST = "localhost";
  public static final int    DEFAULT_PORT = 53115;

  private static HashMap<String, KnowBase> cache = new HashMap<>();

  private String         path;
  private Socket         socket;
  private PrintWriter    output;
  private BufferedReader input;


  private KnowBase(String path, String host, int port) {
    this.path = path;
    try {
      socket = new Socket(host, port);
      output = new PrintWriter(socket.getOutputStream());
      input  = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    }
    catch (IOException e) {
      throw new CommunicationException(e);
    }
  }

  public static synchronized KnowBase of(String path) {
    KnowBase kb = cache.get(path);
    if (kb != null) {
      return kb;
    }

    String eh   = System.getenv("SEMSERV_HOST");
    String ep   = System.getenv("SEMSERV_PORT");
    String host = eh == null ? DEFAULT_HOST : eh;
    int    port = ep == null ? DEFAULT_PORT : Integer.parseInt(ep);

    kb = new KnowBase(path, host, port);
    cache.put(path, kb);
    return kb;
  }


  private synchronized JsonElement msg(String op, JsonElement arg) {
    JsonArray req = new JsonArray();
    req.add(path);
    req.add(op);
    req.add(arg);

    output.println(req.toString());
    output.flush();

    JsonElement res;
    try {
       res = new JsonParser().parse(input.readLine());
    }
    catch (IOException e) {
      throw new CommunicationException(e);
    }

    if (res.isJsonObject()) {
      throw new MessageException(res);
    }

    return res;
  }


  private Set<Individual> toUtil(JsonElement from) {
    Set<Individual> is = new HashSet<Individual>();

    for (JsonElement e : from.getAsJsonArray()) {
      is.add(new Individual(this, e.getAsString()));
    }

    return is;
  }


  private Individual ensureCorrectSource(Individual i) {
    if (!equals(i.getKnowBase())) {
      throw new IncompatibleIndividualException(this, i);
    }
    return i;
  }


  public boolean isSatisfiable(Conceptual c) {
    return msg("satisfiable", c.toJson()).getAsBoolean();
  }

  public boolean isSubtype(Conceptual sub, Conceptual sup) {
    return msg("subtype", Util.allToJson(sub, sup)).getAsBoolean();
  }

  public boolean isMember(Conceptual c, Individual i) {
    JsonArray arg = new JsonArray();
    arg.add(c.toJson());
    arg.add(ensureCorrectSource(i).getIri());
    return msg("member", arg).getAsBoolean();
  }


  public Individual nominal(String iri) {
    checkSignature("individual", iri);
    String actualIri = msg("individual", new JsonPrimitive(iri)).getAsString();
    return new Individual(this, actualIri);
  }


  public Set<Individual> query(Conceptual c) {
    c.checkSignature(this);
    return toUtil(msg("query", c.toJson()));
  }


  public Set<Individual> project(Individual i, Roleish r) {
    r.checkSignature(this);
    JsonArray arg = new JsonArray();
    arg.add(ensureCorrectSource(i).getIri());
    arg.add(r.toJson());
    return toUtil(msg("project", arg));
  }


  public Individual cast(Conceptual c, Object o) {
    Individual i = (Individual) o;
    if (i != null && !isMember(c, i)) {
      throw new SemanticCastException(c, i);
    }
    return i;
  }

  public boolean isInstanceOf(Conceptual c, Object o) {
    return o instanceof Individual && isMember(c, (Individual) o);
  }


  public boolean hasSignature(String type, String iri) {
    JsonArray arg = new JsonArray();
    arg.add(type);
    arg.add(iri);
    return msg("signature", arg).getAsBoolean();
  }

  public void checkSignature(String type, String iri) {
    if (!hasSignature(type, iri)) {
      Logger.getGlobal().warning(
          String.format("%s '%s' not in signature of '%s'", type, iri, path));
    }
  }


  @Override
  public String toString() {
    return String.format("KnowBase[%s]", path);
  }

  @Override
  public boolean equals(Object other) {
    if (other instanceof KnowBase) {
      return path.equals(((KnowBase) other).path);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return path.hashCode();
  }
}

@@ -179,32 +179,63 @@
 BufferedReader;%0A
+import java.io.BufferedWriter;%0A
 import java.io.I
@@ -295,21 +295,57 @@
 java.io.
-Print
+OutputStream;%0Aimport java.io.OutputStream
 Writer;%0A
@@ -759,16 +759,71 @@
 oleish;%0A
+import static java.nio.charset.StandardCharsets.UTF_8;%0A
 %0A%0Apublic
@@ -1096,23 +1096,23 @@
 ate 
-Print
+Buffered
 Writer 
-   
 outp
@@ -1273,24 +1273,25 @@
 ost, port);%0A
+%0A
       output
@@ -1301,13 +1301,50 @@
 new 
-Print
+BufferedWriter(%0A          new OutputStream
 Writ
@@ -1370,19 +1370,28 @@
 Stream()
+, UTF_8)
 );%0A
+%0A
       in
@@ -1394,17 +1394,16 @@
   input 
- 
 = new Bu
@@ -1415,16 +1415,27 @@
 dReader(
+%0A          
 new Inpu
@@ -1471,16 +1471,23 @@
 Stream()
+, UTF_8
 ));%0A    
@@ -2168,24 +2168,59 @@
 .add(arg);%0A%0A
+    JsonElement res;%0A%0A    try %7B%0A   
     output.p
@@ -2222,15 +2222,13 @@
 put.
-println
+write
 (req
@@ -2238,19 +2238,29 @@
 String()
-);%0A
+ + %22%5Cn%22);%0A   
     outp
@@ -2274,40 +2274,8 @@
 h();
-%0A%0A    JsonElement res;%0A    try %7B
 %0A   

Use UTF-8 encoding for socket communication