package org.activiti.bpmn.converter;

import java.text.SimpleDateFormat;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import org.activiti.bpmn.converter.util.BpmnXMLUtil;
import org.activiti.bpmn.model.BaseElement;
import org.activiti.bpmn.model.BooleanDataObject;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.DateDataObject;
import org.activiti.bpmn.model.DoubleDataObject;
import org.activiti.bpmn.model.ExtensionElement;
import org.activiti.bpmn.model.IntegerDataObject;
import org.activiti.bpmn.model.ItemDefinition;
import org.activiti.bpmn.model.LongDataObject;
import org.activiti.bpmn.model.StringDataObject;
import org.activiti.bpmn.model.ValuedDataObject;
import org.apache.commons.lang3.StringUtils;

/**
 * @author Lori Small
 * @author Tijs Rademakers
 */
public class ValuedDataObjectXMLConverter extends BaseBpmnXMLConverter {
  
  private final Pattern xmlChars = Pattern.compile("[<>&]");
  private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
  protected boolean didWriteExtensionStartElement = false;
  
  public Class<? extends BaseElement> getBpmnElementType() {
    return ValuedDataObject.class;
  }
  
  @Override
  protected String getXMLElementName() {
    return ELEMENT_DATA_OBJECT;
  }
  
  @Override
  protected BaseElement convertXMLToElement(XMLStreamReader xtr, BpmnModel model) throws Exception {
    ValuedDataObject dataObject = null;
    ItemDefinition itemSubjectRef = new ItemDefinition();

    String structureRef = xtr.getAttributeValue(null, ATTRIBUTE_DATA_ITEM_REF);
    if (StringUtils.isNotEmpty(structureRef) && structureRef.contains(":")) {
      String dataType = structureRef.substring(structureRef.indexOf(':') + 1);
      
      if (dataType.equals("string")) {
        dataObject = new StringDataObject();
      } else if (dataType.equals("int")) {
        dataObject = new IntegerDataObject();
      } else if (dataType.equals("long")) {
        dataObject = new LongDataObject();
      } else if (dataType.equals("double")) {
        dataObject = new DoubleDataObject();
      } else if (dataType.equals("boolean")) {
        dataObject = new BooleanDataObject();
      } else if (dataType.equals("datetime")) {
        dataObject = new DateDataObject();
      } else {
        LOGGER.error("Error converting {}, invalid data type: " + dataType, xtr.getAttributeValue(null, ATTRIBUTE_DATA_NAME));
      }
    
    } else {
      // use String as default type
      dataObject = new StringDataObject();
      structureRef = "xsd:string";
    }
    
    if (dataObject != null) {
      dataObject.setId(xtr.getAttributeValue(null, ATTRIBUTE_DATA_ID)); 
      dataObject.setName(xtr.getAttributeValue(null, ATTRIBUTE_DATA_NAME)); 
      
      BpmnXMLUtil.addXMLLocation(dataObject, xtr);

      itemSubjectRef.setStructureRef(structureRef);
      dataObject.setItemSubjectRef(itemSubjectRef); 

      parseChildElements(getXMLElementName(), dataObject, model, xtr);
      
      List<ExtensionElement> valuesElement = dataObject.getExtensionElements().get("value");
      if (valuesElement != null && !valuesElement.isEmpty()) {
        ExtensionElement valueElement = valuesElement.get(0);
        if (StringUtils.isNotEmpty(valueElement.getElementText())) {
          if (dataObject instanceof DateDataObject) {
            try {
              dataObject.setValue(sdf.parse(valueElement.getElementText()));
            } catch (Exception e) {
              LOGGER.error("Error converting {}", dataObject.getName(), e.getMessage());
            }
          } else {
            dataObject.setValue(valueElement.getElementText());
          }
        }
        
        // remove value element
        dataObject.getExtensionElements().remove("value");
      }
    }

    return dataObject;
  }

  @Override
  protected void writeAdditionalAttributes(BaseElement element, BpmnModel model, XMLStreamWriter xtw) throws Exception {
    ValuedDataObject dataObject = (ValuedDataObject) element;
    if (dataObject.getItemSubjectRef() != null && StringUtils.isNotEmpty(dataObject.getItemSubjectRef().getStructureRef())) {
      writeDefaultAttribute(ATTRIBUTE_DATA_ITEM_REF, dataObject.getItemSubjectRef().getStructureRef(), xtw);
    }
  }

  @Override
  protected boolean writeExtensionChildElements(BaseElement element, boolean didWriteExtensionStartElement, XMLStreamWriter xtw) throws Exception {
    ValuedDataObject dataObject = (ValuedDataObject) element;

    if (StringUtils.isNotEmpty(dataObject.getId()) && dataObject.getValue() != null) {

      if (didWriteExtensionStartElement == false) { 
        xtw.writeStartElement(ELEMENT_EXTENSIONS);
        didWriteExtensionStartElement = true;
      }

      xtw.writeStartElement(ACTIVITI_EXTENSIONS_PREFIX, ELEMENT_DATA_VALUE, ACTIVITI_EXTENSIONS_NAMESPACE);
      if (dataObject.getValue() != null) {
        String value = null;
        if (dataObject instanceof DateDataObject) {
          value = sdf.format(dataObject.getValue());
        } else {
          value = dataObject.getValue().toString();
        }

        if (dataObject instanceof StringDataObject && xmlChars.matcher(value).find())
        {
          xtw.writeCData(value);
        }
        else
        {
          xtw.writeCharacters(value);
        }
      }
      xtw.writeEndElement();
    }
    
    return didWriteExtensionStartElement;
  }

  @Override
  protected void writeAdditionalChildElements(BaseElement element, BpmnModel model, XMLStreamWriter xtw) throws Exception {
  }
}
