/* 
 * Copyright 2015 by AVM GmbH <info@avm.de>
 *
 * This software contains free software; you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License ("License") as 
 * published by the Free Software Foundation  (version 3 of the License). 
 * This software is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the copy of the 
 * License you received along with this software for more details.
 */

package de.avm.android.tr064.sax;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import de.avm.android.tr064.Tr064Log;
import de.avm.android.tr064.exceptions.DataMisformatException;
import de.avm.android.tr064.model.Call;
import de.avm.android.tr064.model.CallLog;

/*
 * SAX-Handler for AVM Calllist-Format
 */
public class SAXCallLogHandler extends DefaultHandler
{
    public static final String TAG = "SAXCallLogHandler";

	private enum FieldTag
	{
		TYPE, ID, CALLER, CALLED, NAME, NUMBERTYPE,
		DEVICE, DATE, DURATION, COUNT, PATH, PORT
	}

	private FieldTag mInCallTag = null;
	private CallLog mCallLog = null;
	private int mNextActiveCallInsert = 0;
	private Call currentCall = null;
	private String mCurrentString = "";		// no nested tags!!
    private boolean mInListTimestamp = false;

	/**
	 * Instantiates a new sAX call log handler.
	 * 
	 * @param callLog
	 *            the call log
	 */
	public SAXCallLogHandler(CallLog callLog)
	{
		mCallLog = callLog;
	}

	@Override
	public void startElement(String namespaceURI, String localName,
			String qName, Attributes atts) throws SAXException
	{
        if (localName.equalsIgnoreCase("call"))
        {
            currentCall = new Call();
            mInCallTag = null;
            mInListTimestamp = false; // error?
        }
		else if (currentCall != null)
		{
			if (mInCallTag != null)
				throw new DataMisformatException(String.format(
						"Nested call fields not supported. Tag: \"%s\"", localName));
			try
			{
				mInCallTag = FieldTag.valueOf(localName.toUpperCase(Locale.US));
				mCurrentString = "";
			}
			catch(Exception e)
			{
				// ignore unknown tags
			}
		}
        else if (localName.equalsIgnoreCase("timestamp"))
        {
            mInListTimestamp = true;
            mCurrentString = "";
        }
}

	@Override
	public void endElement(String namespaceURI, String localName, String qName)
			throws SAXException
	{
		if (localName.equalsIgnoreCase("call"))
		{
			if (currentCall.getType().isActive())
			{
				try
				{
					mCallLog.insertCall(mNextActiveCallInsert, currentCall);
					mNextActiveCallInsert++;
				}
				catch(IndexOutOfBoundsException e)
				{
					// should not occur
					mCallLog.addCall(currentCall);
				}
			}
			else mCallLog.addCall(currentCall);
			currentCall = null;
			return;
		}
        else if (mInListTimestamp && (localName.equalsIgnoreCase("timestamp")))
        {
            mInListTimestamp = false;
            try
            {
                mCallLog.setTimestamp(Long.parseLong(mCurrentString));
            }
            catch (NumberFormatException e)
            {
                // ignore illegal value
                mCallLog.setTimestamp(0);
            }
            return;
        }
		else if (currentCall == null)
		{
			return; // not within a call
		}

		FieldTag tag = null; 
		try
		{
			tag = FieldTag.valueOf(localName.toUpperCase(Locale.US));
		}
		catch(Exception e)
		{
			// ignore unknown tags
		}
		
		if ((tag != null) && tag.equals(mInCallTag))
		{
			mInCallTag = null;
			switch (tag)	
			{
                case ID:
                    try
                    {
                        currentCall.setUniqueId(Long.parseLong(mCurrentString));
                    }
                    catch (NumberFormatException e)
                    {
//                        throw new DataMisformatException("Invalid Id", e);
                        // tolerate invalid content
                        Tr064Log.w(TAG, "Invalid call Id (" + localName + ")\"" +
                                mCurrentString + "\", using \"0\"", e);
                        currentCall.setUniqueId(0);
                    }
                    break;
				case TYPE:
					currentCall.setType(Call
							.getCallTypeForKey(mCurrentString));
					break;
				case DATE:
					try
					{
						SimpleDateFormat formatter =
							new SimpleDateFormat("dd.MM.yy HH:mm", Locale.US);
						currentCall.setTimeStamp(formatter
								.parse(mCurrentString));
					}
					catch (ParseException e)
					{
						throw new DataMisformatException("Invalid DateType", e);
					}
					break;
				case DURATION:
					currentCall.setDuration(parseDuration(mCurrentString));
					break;
				case COUNT:
					try
					{
						currentCall.setCount(Integer.parseInt(mCurrentString));
					}
					catch (NumberFormatException e)
					{
						// ignore illegal value
						currentCall.setCount(0);
					}
					break;
			}
		}
	}

	@Override
	public void characters(char ch[], int start, int length)
	{
        String string = new String(ch, start,length);
		if (mInCallTag != null)
		{
			switch (mInCallTag)
			{
				case CALLER:
					currentCall.setCallerNumber(currentCall.getCallerNumber() +
							string);
					break;
				case CALLED:
					currentCall.setCalledNumber(currentCall.getCalledNumber() +
							string);
					break;
				case NAME:
					currentCall.setPartnerName(currentCall.getPartnerName() +
							string);
					break;
				case DEVICE:
					currentCall.setInternPortName(currentCall.getInternPortName() +
							string);
					break;
				case PORT:
					currentCall.setInternPort(currentCall.getInternPort() +
							string);
					break;
				case PATH:
					currentCall.setPath(currentCall.getPath() + string);
					break;
				default:
					mCurrentString += string;
					break;
			}
		}
        else if (mInListTimestamp)
        {
            mCurrentString += string;
        }
	}

	/**
	 * Parses the duration.
	 * 
	 * @param durationFormatted
	 *            the duration formatted
	 * 
	 * @return the integer
	 */
	protected static Integer parseDuration(String durationFormatted)
	{
		final int[] time_multiplicators = { 1, 60, 1440 };
		int sum = 0;

		String[] parts = durationFormatted.split(":");
		if (parts.length == 0 || parts.length > 3)
			throw new DataMisformatException("Duration not valid");
		int j = 0;
		for (int i = parts.length - 1; i >= 0; i--)
		{
			sum += Integer.parseInt(parts[i]) * time_multiplicators[j];
			j++;
		}
		return sum;
	}
}
