/*
* Copyright 2014 - LAit SpA
*				 Realizzato da Icona Management srl su commessa LAit SpA
* Copyright 2015- LAit SpA
*                                                  Aggiornato da LAit SpA
*
* Concesso in licenza a norma dell'EUPL, versione 1.1 o
* successive dell'EUPL (la "Licenza")– non appena saranno
* approvate dalla Commissione europea;
* Non è possibile utilizzare l'opera salvo nel rispetto della Licenza.
* È possibile ottenere una copia della Licenza al seguente indirizzo:
*
* http://ec.europa.eu/idabc/eupl
*
* Salvo diversamente indicato dalla legge applicabile o
* concordato per iscritto, il software distribuito secondo
* i termini della Licenza è distribuito "TAL QUALE",
* SENZA GARANZIE O CONDIZIONI DI ALCUN TIPO,
* esplicite o implicite.
* Si veda la Licenza per la lingua specifica che disciplina
* le autorizzazioni e le limitazioni secondo i termini della
* Licenza.
*/
package it.laitspa.cpf.util.misc;

import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

public class XmlWriter
{
  private Writer        writer;
  private Stack<String> stack;
  private StringBuffer  attrs;
  private boolean       empty;
  private boolean       closed;
  private boolean       inline;

  private static String nl  = "\n";
  private static String tab = "  ";

  public XmlWriter(Writer writer)
  {
    this.writer = writer;
    closed = true;
    stack = new Stack<String>();
  }

  public XmlWriter writeXMLDeclaration()
    throws IOException
  {
    writer.write("<?xml version=\"1.0\" encoding=\"ISO-8859-1\" standalone=\"yes\"?>");
    writer.write(nl);

    return this;
  }

  public XmlWriter writeEntity(String name)
    throws IOException
  {
    closeOpeningTag();
    writer.write(nl);
    closed = false;
    inline = false;
    for(int i = 0; i < stack.size(); ++i)
      writer.write(tab);
    writer.write("<");
    writer.write(name);
    stack.add(name);
    empty = true;
    return this;
  }

  public XmlWriter writeEntityWithAttributes(String name, Map<String, String> attributes)
    throws IOException
  {
    closeOpeningTag();
    writer.write(nl);
    closed = false;
    inline = false;
    for(int i = 0; i < stack.size(); ++i)
      writer.write(tab);
    writer.write("<");
    writer.write(name);
    stack.add(name);
    empty = true;

    Set<String> attKeys = attributes.keySet();
    Iterator<String> ic = attKeys.iterator();
    while(ic.hasNext())
    {
      String key = (String)ic.next();
      String value = (String)attributes.get(key);
      writer.write(" ");
      writer.write(key);
      writer.write("=\"");
      writer.write(escapeXml(value));
      writer.write("\"");
    }

    return this;
  }

  private void closeOpeningTag()
    throws IOException
  {
    if(!closed)
    {
      writeAttributes();
      closed = true;
      writer.write(">");
    }
  }

  private void writeAttributes()
    throws IOException
  {
    if(attrs != null)
    {
      writer.write(attrs.toString());
      attrs.setLength(0);
      empty = false;
    }
  }

  public XmlWriter writeAttribute(String attr, String value)
  {
    if(attrs == null)
    {
      attrs = new StringBuffer();
    }
    attrs.append(" ");
    attrs.append(attr);
    attrs.append("=\"");
    attrs.append(escapeXml(value));
    attrs.append("\"");
    return this;
  }

  public XmlWriter endEntity()
    throws IOException
  {
    if(stack.empty())
      throw new IOException("Called endEntity too many times. ");

    String name = (String)stack.pop();
    if(name != null)
    {
      if(empty)
      {
        writeAttributes();
        writer.write("/>");
      }
      else
      {
        if(!inline)
        {
          writer.write(nl);
          for(int i = 0; i < stack.size(); ++i)
            writer.write(tab);
        }
        writer.write("</");
        writer.write(name);
        writer.write(">");
      }
      empty = false;
    }

    closed = true;
    inline = false;

    return this;
  }

  public void close()
    throws IOException
  {
    if(!stack.empty())
    {
      throw new IOException("Tags are not all closed. "
          + "Possibly, " + stack.pop() + " is unclosed. ");
    }
  }

  public XmlWriter writeText(String text)
    throws IOException
  {
    if(text == null)
      return this;

    closeOpeningTag();
    empty = false;
    inline = true;
    writer.write(escapeXml(text.trim()));
    return this;
  }

  public XmlWriter writeDate(Date date)
    throws IOException
  {
    if(date == null)
      return this;

    closeOpeningTag();
    empty = false;
    inline = true;
    writer.write(XmlUtils.formatDate(date));
    return this;
  }

  public XmlWriter writeInteger(Integer i)
    throws IOException
  {
    if(i == null)
      return this;

    closeOpeningTag();
    empty = false;
    inline = true;
    writer.write(XmlUtils.formatInteger(i));
    return this;
  }

  public XmlWriter writeSimpleEntity(String name, String text)
    throws IOException
  {
    if(text == null || text.trim().length() == 0)
      return this;

    closeOpeningTag();

    writer.write(nl);

    closed = true;
    inline = false;
    empty = false;

    for(int i = 0; i < stack.size(); ++i)
      writer.write(tab);

    writer.write("<");
    writer.write(name);
    writer.write(">");

    writer.write(escapeXml(text));

    writer.write("</");
    writer.write(name);
    writer.write(">");

    return this;
  }

  public XmlWriter writeSimpleEmptyEntity(String name)
    throws IOException
  {

    closeOpeningTag();

    writer.write(nl);

    closed = true;
    inline = false;
    empty = false;

    for(int i = 0; i < stack.size(); ++i)
      writer.write(tab);

    writer.write("<");
    writer.write(name);
    writer.write(">");

    // writer.write(escapeXml(text));

    writer.write("</");
    writer.write(name);
    writer.write(">");

    return this;
  }

  public XmlWriter writeSimpleNullEntity(String name, String namespace)
    throws IOException
  {

    closeOpeningTag();

    writer.write(nl);

    closed = true;
    inline = false;
    empty = false;
    String nil = "";
    if(namespace != null)
      nil += namespace + ":";
    nil += "nil=\"true\"";

    for(int i = 0; i < stack.size(); ++i)
      writer.write(tab);

    writer.write("<");
    writer.write(name);
    writer.write(" ");
    writer.write(nil);
    writer.write(">");

    writer.write("</");
    writer.write(name);
    writer.write(">");

    return this;
  }

  public XmlWriter writeSimpleEntity(String name, Date date)
    throws IOException
  {
    String text = XmlUtils.formatDate(date);
    return writeSimpleEntity(name, text);
  }

  public XmlWriter writeSimpleEntity(String name, Integer i)
    throws IOException
  {
    String text = XmlUtils.formatInteger(i);
    return writeSimpleEntity(name, text);
  }

  public XmlWriter writeSimpleEntityAmount(String name, BigDecimal bd)
    throws IOException
  {
    String text = XmlUtils.formatAmount(bd);
    return writeSimpleEntity(name, text);
  }

  public XmlWriter writeSimpleEntityPerc(String name, BigDecimal bd)
    throws IOException
  {
    String text = XmlUtils.formatPerc(bd);
    return writeSimpleEntity(name, text);
  }

  public XmlWriter writeSimpleEntityPrice(String name, BigDecimal bd)
    throws IOException
  {
    String text = XmlUtils.formatPrice(bd);
    return writeSimpleEntity(name, text);
  }

  public XmlWriter writeSimpleEntityQty(String name, BigDecimal bd)
    throws IOException
  {
    String text = XmlUtils.formatQty(bd);
    return writeSimpleEntity(name, text);
  }

  static public String escapeXml(String str)
  {
    str = replaceString(str, "&", "&amp;");
    str = replaceString(str, "<", "&lt;");
    str = replaceString(str, ">", "&gt;");
    str = replaceString(str, "\"", "&quot;");
    str = replaceString(str, "'", "&apos;");
    return str;
  }

  static public String replaceString(String text, String repl, String with)
  {
    return replaceString(text, repl, with, -1);
  }

  static public String replaceString(String text, String repl, String with, int max)
  {
    if(text == null)
      return null;

    StringBuffer buffer = new StringBuffer(text.length());
    int start = 0;
    int end = 0;
    while((end = text.indexOf(repl, start)) != -1)
    {
      buffer.append(text.substring(start, end)).append(with);
      start = end + repl.length();

      if(--max == 0)
        break;
    }
    buffer.append(text.substring(start));

    return buffer.toString();
  }
}
