package com.github.imas.rdflint.parser;

import com.github.imas.rdflint.LintProblem;
import com.github.imas.rdflint.LintProblemLocation;
import com.github.imas.rdflint.validator.RdfValidator;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import org.apache.jena.atlas.web.ContentType;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.query.ARQ;
import org.apache.jena.riot.Lang;
import org.apache.jena.riot.RDFParserRegistry;
import org.apache.jena.riot.ReaderRIOT;
import org.apache.jena.riot.ReaderRIOTFactory;
import org.apache.jena.riot.RiotParseException;
import org.apache.jena.riot.system.ErrorHandler;
import org.apache.jena.riot.system.FactoryRDF;
import org.apache.jena.riot.system.IRIResolver;
import org.apache.jena.riot.system.ParserProfileStd;
import org.apache.jena.riot.system.PrefixMap;
import org.apache.jena.riot.system.PrefixMapFactory;
import org.apache.jena.riot.system.RiotLib;
import org.apache.jena.riot.system.StreamRDFLib;
import org.apache.jena.riot.tokens.Token;
import org.apache.jena.riot.tokens.TokenType;
import org.apache.jena.sparql.util.Context;
import org.apache.log4j.Logger;

public class RdflintParserTurtle extends RdflintParser {

  private static final Logger logger = Logger.getLogger(RdflintParserTurtle.class.getName());

  static class RdflintParseProfile extends ParserProfileStd {

    List<LintProblem> diagnosticList;
    List<RdfValidator> validationModels;

    public RdflintParseProfile(FactoryRDF factory, ErrorHandler errorHandler, IRIResolver resolver,
        PrefixMap prefixMap, Context context, boolean checking, boolean strictMode,
        List<RdfValidator> validationModels, List<LintProblem> diagnosticList) {
      super(factory, errorHandler, resolver, prefixMap, context, checking, strictMode);
      this.validationModels = validationModels;
      this.diagnosticList = diagnosticList;
    }

    @Override
    public Triple createTriple(Node subject, Node predicate, Node object, long line, long col) {
      validationModels.forEach(m -> {
        if (object.isLiteral()) {
          int length = object.getLiteralLexicalForm().length();
          diagnosticList.addAll(m.validateTriple(
              subject, predicate, object,
              (int) line, (int) col, (int) line, (int) col + length));
        }
      });
      return super.createTriple(subject, predicate, object, line, col);
    }

    private String expandPrefixedName(String prefix, String localPart) {
      String expansion = getPrefixMap().expand(prefix, localPart);
      if (expansion == null) {
        if (ARQ.isTrue(ARQ.fixupUndefinedPrefixes)) {
          return RiotLib.fixupPrefixIRI(prefix, localPart);
        }
      }
      return expansion;
    }

    @Override
    public Node create(Node currentGraph, Token token) {
      int line = (int) token.getLine();
      int col = (int) token.getColumn();
      String str = token.getImage();
      validationModels.forEach(m -> {
        Node node = null;
        int length = 0;
        if (token.getType() == TokenType.PREFIXED_NAME) {
          String prefix = str;
          String suffix = token.getImage2();
          String expansion = expandPrefixedName(prefix, suffix);
          node = createURI(expansion, line, col);
          length = str.length() + suffix.length();
        } else if (token.getType() == TokenType.IRI) {
          node = createURI(str, line, col);
          length = str.length();
        }
        if (node != null) {
          List<LintProblem> diagnostic = m
              .validateNode(node, line, col, line, col + length);
          diagnosticList.addAll(diagnostic);
        }
      });
      return super.create(currentGraph, token);
    }
  }

  String text;
  List<RdfValidator> validators;

  /**
   * constructor.
   */
  public RdflintParserTurtle(String text, List<RdfValidator> validators) {
    super();
    this.text = text;
    this.validators = validators;
  }

  @Override
  public void parse(Graph g, List<LintProblem> problems) {
    List<LintProblem> diagnosticList = new LinkedList<>();
    List<LintProblem> diagnosticErrorList = new LinkedList<>();
    try {
      // validation
      FactoryRDF factory = RiotLib.factoryRDF();
      IRIResolver resolver = IRIResolver.create();
      PrefixMap prefixMap = PrefixMapFactory.createForInput();
      Context context = new Context();
      boolean checking = true;
      boolean strict = false;
      RdflintParseProfile profile = new RdflintParseProfile(
          factory,
          new RdflintParserErrorHandler(diagnosticErrorList),
          resolver,
          prefixMap,
          context,
          checking,
          strict,
          this.validators,
          diagnosticList);

      ReaderRIOTFactory r = RDFParserRegistry.getFactory(Lang.TURTLE);
      ReaderRIOT reader = r.create(Lang.TURTLE, profile);
      ContentType ct = Lang.TURTLE.getContentType();
      InputStream validateIn = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
      reader.read(validateIn, null, ct, StreamRDFLib.graph(g), context);

      if (!diagnosticErrorList.isEmpty()) {
        problems.addAll(diagnosticErrorList);
        return;
      }
      problems.addAll(diagnosticList);

    } catch (RiotParseException ex) {
      if (!diagnosticErrorList.isEmpty()) {
        problems.addAll(diagnosticErrorList);
        return;
      }
      problems.add(new LintProblem(
          LintProblem.ErrorLevel.ERROR, null,
          new LintProblemLocation((int) ex.getLine(), (int) ex.getCol()),
          null, ex.getMessage()));

    } catch (Exception ex) {
      if (!diagnosticErrorList.isEmpty()) {
        problems.addAll(diagnosticErrorList);
        return;
      }
      String msg = ex.getMessage() != null ? ex.getMessage() : ex.toString();
      if (logger.isTraceEnabled()) {
        logger.trace("parse error: " + msg);
        problems.add(new LintProblem(
            LintProblem.ErrorLevel.ERROR, null,
            new LintProblemLocation(1, 1),
            null, msg));
      }
    }

  }

}