package com.willwinder.universalgcodesender.pendantui;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.glxn.qrgen.QRCode;
import net.glxn.qrgen.image.ImageType;

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.resource.Resource;

import com.google.gson.Gson;
import com.willwinder.universalgcodesender.model.BackendAPI;
import com.willwinder.universalgcodesender.model.UnitUtils.Units;
import com.willwinder.universalgcodesender.MacroHelper;
import com.willwinder.universalgcodesender.i18n.Localization;
import com.willwinder.universalgcodesender.listeners.ControllerListener;
import com.willwinder.universalgcodesender.listeners.ControllerStatus;
import com.willwinder.universalgcodesender.model.Position;
import com.willwinder.universalgcodesender.model.UGSEvent;
import com.willwinder.universalgcodesender.types.GcodeCommand;
import java.net.URL;
import java.util.logging.Level;

/**
 * This class will launch a local webserver which will provide a simple pendant interface
 * @author bobj
 *
 */
public class PendantUI implements ControllerListener {
    private static final Logger logger = Logger.getLogger(PendantUI.class.getName());
    private BackendAPI mainWindow;
    private Server server = null;
    private int port = 8080;
    private SystemStateBean systemState = new SystemStateBean();
    
    public PendantUI(BackendAPI mainWindow) {
        this.mainWindow = mainWindow;
    }

    public Resource getBaseResource(){
        try {
            URL res = getClass().getResource("/resources/pendantUI");
            return Resource.newResource(res);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    
    /**
     * Launches the local web server.
     * @return the url for the pendant interface
     */
    public List<PendantURLBean> start(){
        server = new Server(port);

        ResourceHandler resourceHandler = new ResourceHandler();
        resourceHandler.setDirectoriesListed(false);
        resourceHandler.setWelcomeFiles(new String[]{ "index.html" });
        resourceHandler.setBaseResource(getBaseResource());
        resourceHandler.setDirectoriesListed(true);

        ContextHandler sendGcodeContext = new ContextHandler();
        sendGcodeContext.setContextPath("/sendGcode");
        sendGcodeContext.setBaseResource(getBaseResource());
        sendGcodeContext.setClassLoader(Thread.currentThread().getContextClassLoader());
        sendGcodeContext.setHandler(new SendGcodeHandler());

        ContextHandler adjustManualLocationContext = new ContextHandler();
        adjustManualLocationContext.setContextPath("/adjustManualLocation");
        adjustManualLocationContext.setBaseResource(getBaseResource());
        adjustManualLocationContext.setClassLoader(Thread.currentThread().getContextClassLoader());
        adjustManualLocationContext.setHandler(new AdjustManualLocationHandler());

        ContextHandler getSystemStateContext = new ContextHandler();
        getSystemStateContext.setContextPath("/getSystemState");
        getSystemStateContext.setBaseResource(getBaseResource());
        getSystemStateContext.setClassLoader(Thread.currentThread().getContextClassLoader());
        getSystemStateContext.setHandler(new GetSystemStateHandler());

        ContextHandler configContext = new ContextHandler();
        configContext.setContextPath("/config");
        configContext.setBaseResource(getBaseResource());
        configContext.setClassLoader(Thread.currentThread().getContextClassLoader());
        configContext.setHandler(new ConfigHandler());
        configContext.setInitParameter("cacheControl", "max-age=0, public");

        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] {configContext, sendGcodeContext, adjustManualLocationContext, getSystemStateContext, resourceHandler, new DefaultHandler()});

        server.setHandler(handlers);

        try {
                server.start();
        } catch (Exception e) {
                throw new RuntimeException(e);
        }

        return getUrlList();
    }
    
    /**
     * Unfortunately, this is not as simple as it seems... since you can have multiple addresses and some of those may not be available via wireless
     * 
     * @return
     */
    public List<PendantURLBean> getUrlList(){
        List<PendantURLBean> out = new ArrayList<>();
        
        Enumeration<NetworkInterface> networkInterfaceEnum;
        try {
            networkInterfaceEnum = NetworkInterface.getNetworkInterfaces();
        } catch (SocketException e) {
            throw new RuntimeException(e);
        }
        while(networkInterfaceEnum.hasMoreElements())
        {
            NetworkInterface networkInterface = networkInterfaceEnum.nextElement();

            Enumeration<InetAddress> addressEnum = networkInterface.getInetAddresses();
            while(addressEnum.hasMoreElements())
            {
                InetAddress addr = addressEnum.nextElement();
                String hostAddress = addr.getHostAddress();
               if(!hostAddress.contains(":") && 
                       !hostAddress.equals("127.0.0.1")){
                   String url = "http://"+hostAddress+":"+port;
                   ByteArrayOutputStream bout = QRCode.from(url).to(ImageType.PNG).stream();
                   out.add(new PendantURLBean(url, bout.toByteArray()));
                   System.out.println("Listening on: "+url);
               }
            }
        }
        
        return out;
    }

    public class SendGcodeHandler extends AbstractHandler{
        @Override
        public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
            baseRequest.setHandled(true);
            String gCode = baseRequest.getParameter("gCode");
            
            try {
                if(isManualControlEnabled()){
                    switch (gCode) {
                        case "$H":
                            mainWindow.performHomingCycle();
                            break;
                        case "$X":
                            mainWindow.killAlarmLock();
                            break;
                        case "$C":
                            mainWindow.toggleCheckMode();
                            break;
                        case "RESET_ZERO":
                            mainWindow.resetCoordinatesToZero();
                            break;
                        case "RETURN_TO_ZERO":
                            mainWindow.returnToZero();
                            break;
                        case "SEND_FILE":
                            mainWindow.send();
                            break;
                        case "PAUSE_RESUME_FILE":
                        case "CANCEL_FILE":
                            break;
                        default:
                            try {
                                MacroHelper.executeCustomGcode(gCode, mainWindow);
                            } catch (Exception ex) {
                        	    System.err.println("pendant failed executing gCode [" + gCode + "]");
                        	    ex.printStackTrace();
                            }
                            break;
                    }
                } else {
                switch (gCode) {
                    case "PAUSE_RESUME_FILE":
                        mainWindow.pauseResume();
                        break;
                    case "CANCEL_FILE":
                        mainWindow.cancel();
                        break;
                    default:
                        break;
                    }
                }
            } catch (Exception e) {
                logger.log(Level.WARNING, "Exception in pendant.", e);
                logger.warning(Localization.getString("SendGcodeHandler"));
            }

            response.getWriter().print(getSystemStateJson());
        }
    }
    
    public class GetSystemStateHandler extends AbstractHandler{
        @Override
        public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
            baseRequest.setHandled(true);
            mainWindow.updateSystemState(systemState);
            response.getWriter().print(getSystemStateJson());
        }
    }
    
    public String getSystemStateJson(){
        return new Gson().toJson(systemState);
    }
    
    
    public class ConfigHandler extends AbstractHandler{
        @Override
        public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
            baseRequest.setHandled(true);
            response.setContentType("application/json");
            response.getWriter().print(new Gson().toJson(mainWindow.getSettings().getPendantConfig()));
        }
    }

    public class AdjustManualLocationHandler extends AbstractHandler{
        @Override
        public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException {
            baseRequest.setHandled(true);
            
            if(isManualControlEnabled()){
                int dirX = parseInt(baseRequest.getParameter("dirX"));
                int dirY = parseInt(baseRequest.getParameter("dirY"));
                int dirZ = parseInt(baseRequest.getParameter("dirZ"));
                double stepSize = parseDouble(baseRequest.getParameter("stepSize"));

                try {
                    mainWindow.adjustManualLocation(dirX, dirY, dirZ, stepSize, 1, Units.UNKNOWN);
                } catch (Exception e) {
                    logger.warning(e.getMessage());
                }
            }

            response.getWriter().print(systemState.getControlState().name());
        }
    }
    
    public int parseInt(String string){
        int out = 0;
        
        try {
            out = Integer.parseInt(string);
        } catch (Exception e) {
            // nothing to do
        }
        return out;
    }
    
    public double parseDouble(String string){
        double out = 0.0;
        try {
            out = Double.parseDouble(string);
        } catch (Exception e) {
            // nothing to do
        }
        return out;
    }
    
    

    public void stop(){
        try {
            if(server!=null){
                server.stop();
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public boolean isManualControlEnabled() {
        switch (systemState.getControlState()) {
        case COMM_DISCONNECTED:
            return false;
        case COMM_IDLE:
            return true;
        case COMM_SENDING:
            return false;
        default:
            return true;
        }
    }

    public BackendAPI getMainWindow() {
        return mainWindow;
    }

    public Server getServer() {
        return server;
    }

    public void setServer(Server server) {
        this.server = server;
    }

    public void setMainWindow(BackendAPI mainWindow) {
        this.mainWindow = mainWindow;
    }

    @Override
    public void controlStateChange(UGSEvent.ControlState state) {
    }

    @Override
    public void fileStreamComplete(String filename, boolean success) {
    }

    @Override
    public void commandSkipped(GcodeCommand command) {
    }

    @Override
    public void commandSent(GcodeCommand command) {
    }

    @Override
    public void commandComplete(GcodeCommand command) {
    }

    @Override
    public void commandComment(String comment) {
    }

    @Override
    public void probeCoordinates(Position p) {
    }

    @Override
    public void messageForConsole(MessageType type, String msg) {
    }

    @Override
    public void statusStringListener(ControllerStatus status) {
        // TODO Auto-generated method stub
        
    }

    @Override
    public void postProcessData(int numRows) {
    }

    public SystemStateBean getSystemState() {
        return systemState;
    }

    public void setSystemState(SystemStateBean systemState) {
        this.systemState = systemState;
    }
}
