package hr.fer.labust.diverapp;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

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

import java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import hr.fer.labust.diverapp.agent.AgentList;
import hr.fer.labust.diverapp.common.ChatMessage;
import hr.fer.labust.diverapp.common.Constants;

public class XMLParser extends Thread{
    private final BlockingQueue<String> mQueue;
    private final Handler mUIHandler;
    private Handler mUpdateHandler;
    private final Context mContext;
    private final AgentList mAgentList = AgentList.getInstace();
    private final MainActivity mActivity;

    private final SAXParserFactory mSAXParserFactory;
    private final SAXParser mSAXParser;

    private final BlockingQueue<Runnable> UIupdates;
    private UIUpdate UIupdateThread;

    private class SAXBreakException extends SAXException {};

    XMLParser(Context context, BlockingQueue<String> queue, Handler uiHandler, MainActivity activity) {
        mQueue = queue;
        mUIHandler = uiHandler;
        mContext = context;
        mActivity = activity;

        UIupdates = new ArrayBlockingQueue<Runnable>(500);
        mSAXParserFactory = SAXParserFactory.newInstance();
        SAXParser tempSAXParser = null;
        try {
            tempSAXParser = mSAXParserFactory.newSAXParser();
        }
        catch (ParserConfigurationException e) {}
        catch (SAXException e2) {}

        mSAXParser = tempSAXParser;

        UIupdateThread = new UIUpdate();
        UIupdateThread.start();
    }

    @Override
    public void run() {
        String message = null;
        Looper.prepare();
        mUpdateHandler = new Handler(Looper.getMainLooper());

        while (true) {
            try {
                message = mQueue.take();
                mSAXParser.parse(new InputSource(new StringReader(message)), mHandler);
                message = null;
            }
            catch (InterruptedException e) {
                UIupdateThread.Stop();
                break;
            }
            catch (SAXException e) {
                if (message != null) {
                    mUIHandler.obtainMessage(Constants.UI_ADD_MESSAGE, "Error while parsing:\n" + message).sendToTarget();
                }
            }
            catch (IOException e) {}
        }
    }

    private final DefaultHandler mHandler = new DefaultHandler() {
        private boolean isMessage = false;
        private String from;
        private String name;
        private boolean updateMap = false;

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes)
                throws SAXException {
            if (qName.equals("DiverApp")) {
                from = attributes.getValue("from");

                if (from == null) {
                    throw new SAXBreakException();
                }
            }
            else if (qName.equals("Position")) {
                String latitude = null;
                String longitude = null;

                int nAttributes = attributes.getLength();
                for (int i=0; i<nAttributes; ++i) {
                    if (attributes.getQName(i).equals("latitude")) {
                        latitude = attributes.getValue(i);
                    }
                    else if (attributes.getQName(i).equals("longitude")) {
                        longitude = attributes.getValue(i);
                    }
                    else {
                        UIupdates.add(new MyRunnable(from, qName, attributes.getQName(i), attributes.getValue(i)));
                    }
                }

                if ((latitude != null) && (longitude != null)) {
                    UIupdates.add(new MyRunnable2(from, latitude, longitude));
                    updateMap = true;
                }
            }
            else if (qName.equals("Operation")) {
                String destination_lat = null;
                String destination_lng = null;

                int nAttributes = attributes.getLength();
                for (int i=0; i<nAttributes; ++i) {
                    if (attributes.getQName(i).equals("destination_latitude")) {
                        destination_lat = attributes.getValue(i);
                    }
                    else if (attributes.getQName(i).equals("destination_longitude")) {
                        destination_lng = attributes.getValue(i);
                    }
                    else {
                        UIupdates.add(new MyRunnable(from, qName, attributes.getQName(i), attributes.getValue(i)));
                    }
                }

                if ((destination_lat != null) && (destination_lng != null)) {
                    UIupdates.add(new MyRunnable3(from, destination_lat, destination_lng));
                    updateMap = true;
                }
            }
            else if (qName.equals("Message")) {
                isMessage = true;

                name = attributes.getValue("name");
                if (name == null) {
                    name = from;
                }
            }
            else if (qName.equals("Flag")) {
                String id = attributes.getValue("id");
                String description = attributes.getValue("description");

                if (id != null) {
                    if (id.equals("message_start")) {
                        mUIHandler.obtainMessage(Constants.FLAG, Constants.FLAG_MESSAGE_START, 0, description).sendToTarget();
                    }
                    else if (id.equals("internal_error")) {
                        mUIHandler.obtainMessage(Constants.FLAG, Constants.FLAG_ERROR, 0, description).sendToTarget();
                    }
                    else if (id.equals("command_acknowledged")) {
                        mUIHandler.obtainMessage(Constants.FLAG, Constants.FLAG_ACKNOWLEDGED, 0, description).sendToTarget();
                    }
                    else if (id.equals("lost_communication")) {
                        mUIHandler.obtainMessage(Constants.FLAG, Constants.FLAG_LOST_COMMUNICATION, 0, description).sendToTarget();
                    }
                }
            }
            else if (qName.equals("Command")) {
            }
            else if (qName.equals("Thruster")) {
                UIupdates.add(new MyRunnable(from, qName, attributes.getValue(0), attributes.getValue(1)));
            }
            else {
                int nAttributes = attributes.getLength();
                for (int i = 0; i < nAttributes; ++i) {
                    UIupdates.add(new MyRunnable(from, qName, attributes.getQName(i), attributes.getValue(i)));
                  }

                return;
            }
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {
            if (isMessage) {
                String text = new String(ch, start, length);
                mUIHandler.obtainMessage(Constants.UI_ADD_CHAT_MESSAGE, new ChatMessage(name, text)).sendToTarget();

                name = null;
                isMessage = false;
            }
        }

        @Override
        public void endDocument() throws SAXException {
            if (updateMap) {
                mUIHandler.obtainMessage(Constants.UI_UPDATE_STATS, 1, 0, from).sendToTarget();
            } else {
                mUIHandler.obtainMessage(Constants.UI_UPDATE_STATS, 0, 0, from).sendToTarget();
            }
        }
    };

    private class MyRunnable implements Runnable {
        String name;
        String group;
        String attribute;
        String value;

        MyRunnable() {}

        public MyRunnable (String name, String group, String attribute, String value) {
            setData(name, group, attribute, value);
        }

        public void setData(String name, String group, String attribute, String value) {
            this.name = name;
            this.group = group;
            this.attribute = attribute;
            this.value = value;
        }

        @Override
        public void run() {
            mAgentList.put(name, group, attribute, value);
        }
    }
    MyRunnable setDataMethod = new MyRunnable();

    private class MyRunnable2 implements Runnable {
        String name;
        String latitude;
        String longitude;

        public MyRunnable2(String name, String latitude, String longitude) {
            setData(name, latitude, longitude);
        }

        public void setData(String name, String latitude, String longitude) {
            this.name = name;
            this.latitude = latitude;
            this.longitude = longitude;
        }

        @Override
        public void run() {
            mAgentList.setPosition(name, latitude, longitude);
        }
    }

    private class MyRunnable3 implements Runnable {
        String name;
        String latitude;
        String longitude;

        public MyRunnable3(String name, String latitude, String longitude) {
            setData(name, latitude, longitude);
        }

        public void setData(String name, String latitude, String longitude) {
            this.name = name;
            this.latitude = latitude;
            this.longitude = longitude;
        }

        @Override
        public void run() {
            mAgentList.setDestination(name, latitude, longitude);
        }
    }

    private class UIUpdate extends Thread {
        boolean stop = false;
        public void Stop() {
            stop = true;
        }

        Runnable r;

        @Override
        public void run() {
            while (!stop) {
                try {
                    r = UIupdates.take();
                    mUIHandler.post(r);
                    r = null;
                }
                catch (InterruptedException e) {
                    break;
                }
            }

            UIupdates.clear();
        }
    }
}
