/*
 * Copyright (c) 2002 - 2011 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.cast.js.html.nu_validator;

import com.ibm.wala.cast.js.html.IHtmlCallback;
import com.ibm.wala.cast.js.html.IHtmlParser;
import com.ibm.wala.cast.js.html.ITag;
import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.tree.impl.LineNumberPosition;
import com.ibm.wala.util.collections.Pair;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import nu.validator.htmlparser.common.XmlViolationPolicy;
import nu.validator.htmlparser.sax.HtmlParser;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

public class NuValidatorHtmlParser implements IHtmlParser {

  @Override
  public void parse(
      final URL url, final Reader reader, final IHtmlCallback handler, final String fileName) {
    URL xx = null;
    try {
      xx = new URL("file://" + fileName);
    } catch (MalformedURLException e1) {
      e1.printStackTrace();
    }
    final URL localFileName = xx;

    HtmlParser parser = new HtmlParser();
    parser.setXmlPolicy(XmlViolationPolicy.ALLOW);
    parser.setContentHandler(
        new ContentHandler() {
          private Locator locator;
          private final ArrayDeque<ITag> tags = new ArrayDeque<>();

          private int countLines(char[] ch, int start, int length) {
            LineNumberReader r =
                new LineNumberReader(new StringReader(new String(ch, start, length)));
            try {
              while (r.read() > -1) ;
            } catch (IOException e) {
              throw new RuntimeException("cannot read from string", e);
            }
            return r.getLineNumber();
          }

          @Override
          public void setDocumentLocator(Locator locator) {
            this.locator = locator;
          }

          @Override
          public void startElement(
              String uri, final String localName, String qName, final Attributes atts)
              throws SAXException {
            final Position line =
                new LineNumberPosition(url, localFileName, locator.getLineNumber());
            tags.push(
                new ITag() {

                  @Override
                  public String getName() {
                    return localName;
                  }

                  @Override
                  public Pair<String, Position> getAttributeByName(String name) {
                    if (atts.getValue(name) != null) {
                      return Pair.make(atts.getValue(name), line);
                    } else {
                      return null;
                    }
                  }

                  @Override
                  public Map<String, Pair<String, Position>> getAllAttributes() {
                    return new AbstractMap<>() {
                      private Set<Map.Entry<String, Pair<String, Position>>> es = null;

                      @Override
                      public Set<java.util.Map.Entry<String, Pair<String, Position>>> entrySet() {
                        if (es == null) {
                          es = new HashSet<>();
                          for (int i = 0; i < atts.getLength(); i++) {
                            final int index = i;
                            es.add(
                                new Map.Entry<>() {

                                  @Override
                                  public String getKey() {
                                    return atts.getLocalName(index).toLowerCase();
                                  }

                                  @Override
                                  public Pair<String, Position> getValue() {
                                    if (atts.getValue(index) != null) {
                                      return Pair.make(atts.getValue(index), line);
                                    } else {
                                      return null;
                                    }
                                  }

                                  @Override
                                  public Pair<String, Position> setValue(
                                      Pair<String, Position> value) {
                                    throw new UnsupportedOperationException();
                                  }
                                });
                          }
                        }
                        return es;
                      }
                    };
                  }

                  @Override
                  public Position getElementPosition() {
                    return line;
                  }

                  @Override
                  public Position getContentPosition() {
                    return line;
                  }
                });

            handler.handleStartTag(tags.peek());
          }

          @Override
          public void endElement(String uri, String localName, String qName) throws SAXException {
            handler.handleEndTag(tags.pop());
          }

          @Override
          public void characters(char[] ch, int start, int length) throws SAXException {
            handler.handleText(
                new LineNumberPosition(
                    url, localFileName, locator.getLineNumber() - countLines(ch, start, length)),
                new String(ch, start, length));
          }

          @Override
          public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
            handler.handleText(
                new LineNumberPosition(url, localFileName, locator.getLineNumber()),
                new String(ch, start, length));
          }

          @Override
          public void startDocument() throws SAXException {
            // do nothing
          }

          @Override
          public void endDocument() throws SAXException {
            // do nothing
          }

          @Override
          public void startPrefixMapping(String prefix, String uri) throws SAXException {
            // do nothing
          }

          @Override
          public void endPrefixMapping(String prefix) throws SAXException {
            // do nothing
          }

          @Override
          public void processingInstruction(String target, String data) throws SAXException {
            // do nothing
          }

          @Override
          public void skippedEntity(String name) throws SAXException {
            // do nothing
          }
        });

    try {
      parser.parse(
          new InputSource(
              new InputStream() {
                @Override
                public int read() throws IOException {
                  int v;
                  do {
                    v = reader.read();
                  } while (v == '\r');
                  return v;
                }
              }));
    } catch (IOException | SAXException e) {
      assert false : e.toString();
    }
  }
}
