/***
 Copyright (c) 2015 CommonsWare, LLC

 Licensed under the Apache License, Version 2.0 (the "License"); you may
 not use this file except in compliance with the License. You may obtain
 a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0
 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

package com.commonsware.cwac.richtextutils;

import android.text.Layout;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.AlignmentSpan;
import android.text.style.BulletSpan;
import android.text.style.CharacterStyle;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.helpers.DefaultHandler;
import java.util.Arrays;
import java.util.Stack;

class XhtmlSaxHandler extends DefaultHandler {
  private static final String[] NO_ITEM_TAGS={"br", "div"};
  private final SpanTagRoster tagRoster;
  private Stack<Item> textStack=new Stack<Item>();
  boolean isInBulletedList=false;

  XhtmlSaxHandler(SpanTagRoster tagRoster) {
    this.tagRoster=tagRoster;

    textStack.push(new Item(null, ""));
  }

  Spannable getContent() {
    return(textStack.peek().getContent());
  }

  public void startElement(String uri, String localName,
                           String name, Attributes a) {
    String style=a.getValue("style");

    if ("ul".equals(name) || "ol".equals(name)) {

      textStack.push(new Item(new ListTypeSpan(name,a), ""));
      isInBulletedList=true;

      Item item=textStack.peek();

      if (item.isCharacterStyle()) {
        item.append("\n");
      }

      if (style!=null) {
        handleAlignment(style);
      }
    }
    else if ("li".equals(name)) {
        textStack.push(new Item(new BulletSpan(), ""));
    }
    else
    if ("div".equals(name) && style!=null) {
      handleAlignment(style);
    }
    else if (Arrays.binarySearch(NO_ITEM_TAGS, name)<=0) {
      Object span=tagRoster.buildSpanForTag(name, a);

      textStack.push(new Item(span, ""));
    }
  }

  public void endElement(String uri, String localName, String name) {
    if ("div".equals(name)) {
      textStack.peek().append("\n\n");
    }
    else if ("br".equals(name)) {
      textStack.peek().append("\n");
    } else {
      if ("li".equals(name)) {
        textStack.peek().append("\n");
      }

      Item toFinish=textStack.pop();
      Item theNewTop=textStack.peek();

      theNewTop.append(toFinish);
    }
  }

  public void characters(char[] ch, int start, int length) {
    textStack.peek().append(new String(ch, start, length));
  }

  public InputSource resolveEntity(String publicId, String systemId)
      throws org.xml.sax.SAXException, java.io.IOException {
    throw new IllegalStateException("Entities are not supported!");
  }

  private void handleAlignment(String style) {
    if (style.startsWith("text-align:")) {
      Layout.Alignment align=translateTextAlign(style.substring(11));

      textStack.push(new Item(new AlignmentSpan.Standard(align), ""));
    }
    else {
      throw new IllegalStateException("Unrecognized <div> style: "+style);
    }
  }

  private Layout.Alignment translateTextAlign(String textAlign) {
    if ("center".equals(textAlign)) {
      return(Layout.Alignment.ALIGN_CENTER);
    }
    else if ("left".equals(textAlign)) {
      return(SpannedUtils.isRTL() ? Layout.Alignment.ALIGN_OPPOSITE : Layout.Alignment.ALIGN_NORMAL);
    }

    return(SpannedUtils.isRTL() ? Layout.Alignment.ALIGN_NORMAL : Layout.Alignment.ALIGN_OPPOSITE);
  }

  private static class Item {
    private final Object activeSpan;
    private final SpannableStringBuilder content;

    Item(Object span, CharSequence initialContent) {
      activeSpan=span;
      content=new SpannableStringBuilder(initialContent);
    }

    void append(CharSequence newContent) {
      content.append(newContent);
    }

    void append(Item item) {
      int start=content.length();

      content.append(item.content);
      content.setSpan(item.activeSpan, start, content.length(),
          Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    }

    Spannable getContent() {
      return(content);
    }

    boolean isCharacterStyle () {
      return(activeSpan!=null && activeSpan instanceof CharacterStyle);
    }
  }
}

@@ -4222,24 +4222,61 @@
 m.content);%0A
+      if (item.activeSpan != null)%0A  
       conten
@@ -4327,16 +4327,18 @@
 ngth(),%0A
+  
         

fix crash #33 (NPE in SpannableStringInternal) by preventing setting null spans