/**
 *  ServingXML
 *  
 *  Copyright (C) 2006  Daniel Parker
 *    daniel.parker@servingxml.com 
 * 
 * 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.servingxml.util.record;

import java.util.List;
import java.util.ArrayList;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import com.servingxml.util.SystemConstants;
import com.servingxml.util.Name;
import com.servingxml.util.ServingXmlException;
import com.servingxml.util.Stack;
import com.servingxml.util.QualifiedName;

public class RecordContentHandler implements ContentHandler {

  private static final int INITIAL_STATE = 1;
  private static final int IGNORE_STATE = 2;
  private static final int RECORD_STATE = 3;
  private static final int FIELD_STATE = 4;
  private static final int ROOT_STATE = 5;

  private final Name recordTypeName;
  private final RecordReceiver recordReceiver;
  private Stack stack = new Stack();

  public RecordContentHandler(Name recordTypeName, RecordReceiver recordReceiver) {
    this.recordTypeName = recordTypeName;
    this.recordReceiver = recordReceiver;
  }

  public void startDocument() throws SAXException {
    stack = new Stack();
    stack.push(StackEntry.INITIAL_ENTRY);
  }

  public void setDocumentLocator(Locator locator) {
  }

  public void startPrefixMapping (String prefix, String uri)
  throws SAXException {
  }

  public void endPrefixMapping (String prefix)
  throws SAXException {
  }

  public void ignorableWhitespace (char ch[], int start, int length)
  throws SAXException {
  }

  public void processingInstruction (String target, String data)
  throws SAXException {
  }

  public void skippedEntity (String name)
  throws SAXException {
  }

  public void startElement(String namespaceUri, String localName, String qName, 
    Attributes atts) throws SAXException {
    final StackEntry parentEntry = (StackEntry)stack.peek();

    int state = parentEntry.state();
    if (state == INITIAL_STATE) {
      stack.push(StackEntry.ROOT_ENTRY);
    } else if (state == ROOT_STATE) {
      Name recordTypeName = new QualifiedName(namespaceUri,localName);
      stack.push(new RecordEntry(recordTypeName, recordReceiver));
    } else if (state == RECORD_STATE) {
      Name fieldName = new QualifiedName(namespaceUri,localName);
      stack.push(new FieldEntry(fieldName,(RecordEntry)parentEntry));
    } else if (state == FIELD_STATE) {
      Name recordTypeName = new QualifiedName(namespaceUri,localName);
      RecordReceiver recRec = new RecordReceiver() {
        public void receiveRecord(Record record) throws ServingXmlException {
          parentEntry.getRecordList().add(record);
        }
      };

      stack.push(new RecordEntry(recordTypeName, recRec));
    } else {
      throw new SAXException("Unrecognized State");
    }
  }

  public void characters(char ch[], int start, int length) throws SAXException {
    StackEntry stackEntry = (StackEntry)stack.peek();
    stackEntry.characters(ch,start,length);
  }

  public void endElement(String namespaceUri, String localName, String qName) throws SAXException {
    StackEntry stackEntry = (StackEntry)stack.pop();
    stackEntry.endElement();
  }

  public void endDocument() throws SAXException {    
    stack.pop();
  }

  interface StackEntry {
    static StackEntry INITIAL_ENTRY = new InitialEntry();
    static StackEntry ROOT_ENTRY = new RootEntry();

    int state();
    void characters(char ch[], int start, int length);
    void endElement() throws SAXException;
    List getRecordList();
  }

  static class InitialEntry implements StackEntry {

    public int state() {
      return INITIAL_STATE;
    }

    public List getRecordList() {
      return null;
    }

    public void characters(char ch[], int start, int length) {
    }

    public void endElement() {
    }
  }

  static class RootEntry implements StackEntry {

    public int state() {
      return ROOT_STATE;
    }

    public List getRecordList() {
      return null;
    }

    public void characters(char ch[], int start, int length) {
    }

    public void endElement() {
    }
  }

  static class FieldEntry implements StackEntry {
    final RecordEntry recordEntry;
    final Name fieldName;
    final StringBuffer buffer;
    List segmentList;

    FieldEntry(Name fieldName, RecordEntry recordEntry) {
      this.fieldName = fieldName;
      this.recordEntry = recordEntry;
      this.buffer = new StringBuffer();
      this.segmentList = null;
    }

    public int state() {
      return FIELD_STATE;
    }

    public List getRecordList() {
      if (segmentList == null) {
        segmentList = new ArrayList();
      }
      return segmentList;
    }

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

    public void endElement() {
      String value = buffer.toString();
      recordEntry.addString(fieldName,value);
      if (segmentList != null) {
        recordEntry.addSegments(fieldName,segmentList);
      }
    }
  }

  static class RecordEntry implements StackEntry {
    final MultivaluedFieldBuilder fieldBuilder;
    final Name recordTypeName;
    final RecordReceiver recordReceiver;

    RecordEntry(Name recordTypeName, RecordReceiver recordReceiver) {
      this.recordTypeName = recordTypeName;
      this.fieldBuilder = new MultivaluedFieldBuilder();
      this.recordReceiver = recordReceiver;
    }

    public int state() {
      return RECORD_STATE;
    }

    public void characters(char ch[], int start, int length) {
    }

    public List getRecordList() {
      return null;
    }

    public void addString(Name fieldName, String s) {
      fieldBuilder.addString(fieldName,s);
    }

    public void addSegments(Name fieldName, List segmentList) {
      fieldBuilder.addSegments(fieldName,segmentList);
    }

    public void endElement() throws SAXException {
      try {
        RecordBuilder recordBuilder = new RecordBuilder(recordTypeName);
        fieldBuilder.updateRecord(recordBuilder);
        Record record = recordBuilder.toRecord();
        recordReceiver.receiveRecord(record);
        fieldBuilder.clear();
      } catch (ServingXmlException e) {
        e.rethrowAsSaxException();
      }
    }
  }
}

