package com.fitbank.uci.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

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

import com.fitbank.common.conectivity.HbSession;
import com.fitbank.hb.persistence.gene.Tchannels;
import com.fitbank.uci.client.UCILogger;
import com.fitbank.uci.common.Constant;
import com.fitbank.uci.common.Parameters;
import com.fitbank.uci.common.UCIException;
import com.fitbank.uci.server.jms.Sender;
import com.fitbank.uci.server.manager.Command;
import com.fitbank.uci.server.manager.DataAccess;
import com.fitbank.uci.server.services.Channel;
import com.fitbank.uci.server.services.Service;

/**
 * Clase que Administra todos los servicios Disponibles en UCI
 *
 * @author Fit-Bank
 */
public class ServiceProvider extends HttpServlet {
    private static final String UNCHECKED = "unchecked";

    //~ Variables de Instancia ***********************************************************************

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    public Map<String, Channel> channels = null;

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private DataAccess daExit = null;

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private List<String> empty = null;

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private String server = "";

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private UCILogger logger;

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    public static String sServer = "";

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    public final static String DUMMY_CON = "com.fitbank.uci.server.Dummy";

    //~ Variables Est�ticas **************************************************************************

    /**
     * @since empty
     */
    private final static long serialVersionUID = 1L;

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private final static String SUCCESS = "0";

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private final static String ERROR = "1";

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private final static String UNFOUNDCHANNEL = "El canal {0} no fue encontrado o no posee servicios";

    /**
     * Campo utilizado para
     *
     * @since empty
     */
    private final static String REQUIRED_CHANNEL = "El canal es requerido";

    //~ M�todos **************************************************************************************

    /**
     * M�todo que permite
     *
     * @param pChannel Valor de
     * @param pDispositivo Valor de
     * @since empty
     * @throws Exception Error
     */
    public void addService(String pChannel, String pDispositivo) throws Exception {
        this.requiredChannel(pChannel);

        Channel ch = this.channels.get(pChannel);

        ch.addService(pDispositivo);
    }

    /**
     * Mï¿½todo que Permite detener todos los servicios levantados en el Servidor
     *
     * @since empty
     */
    @Override
    public void destroy() {
        try {
            Thread.currentThread().setName("UCIEnd");
            this.stopAll();
            this.daExit.close();

        } catch (Exception e) {
            this.logger.severe("Al detener el Servidor: " + e.getMessage());
            this.logger.throwing(e);
        }
    }

    /**
     * Mï¿½todo que indica si el servidor se encuentra en lï¿½nea
     *
     * @param request Valor de la peticiï¿½n
     * @param response Valor de la respuesta
     * @since empty
     * @throws ServletException Error
     * @throws IOException Error
     */
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();

        try {
            out.println("<html>");
            out.println("<head>");
            out.println("<title>UCI STATUS</title>");
            out.println("<meta http-equiv='Content-Type' content='text/html; charset=iso-8859-1'>");
            out.println("<style type='text/css'>");
            out.println("<!-- ");
            out.println("body {height: 100%;margin-left: 5px;margin-top: 5px;margin-right: 0px;margin-bottom: 0px;}");
            out.println(".subj {font-family: tahoma;font-size: 12px; color:#FFF; font-weight:bolder; text-align:center; background-color:#6D936D; padding-left:2px; padding-right:4px;}");
            out.println("td{font-family: tahoma;font-size: 12px; color:#000000; font-weight:normal;})");
            out.println(".listValor{text-align:left; padding-left:2px; padding-right:2px;}");
            out.println(".subtit{text-align:center; background-color:#F7F7F7; font-weight:bolder;}");
            out.println("table{border-collapse:collapse;}");
            out.println("--></style>");
            out.println("</head>");
            out.println("<body>");
            out.println("<form action='FIT' method='post'>");
            out.println("<table width=100% height=90%  border=0 cellpadding=0 cellspacing=0><tr><td>");
            out.println("<center>UCI v 2.0</center>");
            out.println("<BR><BR>");
            out.println("<table width=553 height=59 border=1 cellpadding=0 cellspacing=0 align=center>");
            out.println("<tr>");
            out.println("<td width=495 height=19 class=subj>Descripci&oacute;n </td>");
            out.println("<td width=52 class=subj>Estatus</td>");
            out.println("<td width=52 class=subj>Dispositivos</td>");
            out.println("</tr><tr>");
            out.println("<td height=19 colspan=2 class=subtit>Canales</td>");
            out.println("</tr>");
            for (Channel channel : this.channels.values()) {
                out.println("<tr><td height=19 class=listValor>" + channel.getName() + "-" + channel.getDescription() +
                                "</td>");
                if (channel.isStarted()) {
                    out.println("<td align='center'><img src='green-status.gif' width=20 height=20></td>");
                } else {
                    out.println("<td align='center'><img src='red-status.gif' width=20 height=20></td>");
                }

                out.println("<td align='center'>" + channel.getServices().size() + "</td></tr>");
            }
            out.println("</table></td></tr></table>");
            out.println("</form></body></html>");
        } catch (Exception e) {
            throw new ServletException(e);
        } finally {
            out.close();
        }

    }

    /**
     * Mï¿½todo que Permite la comunicaciï¿½n con el monitor
     *
     * @param request Valor de la peticiï¿½n
     * @param response Valor de la respuesta
     * @since empty
     * @throws ServletException Error
     * @throws IOException Error
     */
    @Override
    @SuppressWarnings(UNCHECKED)
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        Map<String, Object> resp = new HashMap<String, Object>();

        try {
            try {
                InputStream in = request.getInputStream();
                ObjectInputStream oi = new ObjectInputStream(in);
                Map<String, Object> m = (Map<String, Object>) oi.readObject();

                String command = (String) m.get(Constant.COMMAND);

                if (command != null) {
                    Command com = new Command(this);

                    resp = com.setCommand(m);
                }
                resp.put(Constant.RESPONSE, SUCCESS);
                oi.close();
            } catch (Exception e) {
                UCILogger.getInstance().throwing(e);
                resp.clear();
                resp.put(Constant.RESPONSE, ERROR);
                resp.put(Constant.DESCRIPTION, e.getMessage());
            }

            OutputStream out = response.getOutputStream();
            ObjectOutputStream oo = new ObjectOutputStream(out);

            oo.writeObject(resp);
            oo.close();
        } catch (Exception ex) {
            this.logger.throwing(ex);
        } finally {
            new DataAccess().close();
        }
    }

    /**
     * M�todo que Permite obtener todos los canales
     *
     * @param da Valor de
     * @return Entrega un Map con los valores de los canales
     * @since empty
     * @throws Exception Error
     */
    private Map<String, Channel> getChannels(DataAccess da) throws Exception {
        Map<String, Channel> h = Collections.synchronizedMap(new HashMap<String, Channel>());
        List<Tchannels> chan = da.getChannelList(this.server);

        for (Tchannels channel : chan) {
            Channel ch = new Channel(channel, this.server);

            ch.setProvider(this);
            h.put(channel.getPk().getCcanal(), ch);
        }
        return h;
    }

    /**
     * M�todo que Permite obtener un determinado controlador
     *
     * @param pClass nombre de la clase que implementa el controlador
     * @param pName Valor del nombre
     * @param pChannel Valor del canal
     * @param pDispositivo Valor del dispositivo
     * @return Entrega el valor del controlador
     * @since empty
     * @throws Exception Error
     */
    public Controlador getConector(String pClass, String pName, String pChannel, String pDispositivo) throws Exception {
        if (pClass == null) {
            Channel ch = this.channels.get(pChannel);

            ch.removeService(pName);

            if (ch.getServicesCount() == 0) {
                this.channels.remove(pChannel);
            }

            throw new UCIException("UCI-0030",
                            "El servicio no se encuentra disponible para el servidor o ha sido removido");
        }

        Controlador con = (Controlador) Class.forName(pClass, true, this.getClass().getClassLoader()).newInstance();

        con.setConnectorName(pName);
        con.setChannel(pChannel);
        con.setDispositivo(pDispositivo);
        con.setServer(this.server);
        con.setProvider(this);

        return con;
    }

    /**
     * M�todo que Permite obtener el Data access
     *
     * @return Entrega el valor del data access
     * @since empty
     */
    public DataAccess getDaExit() {
        return this.daExit;
    }

    /**
     * M�todo que Permite obtener el nombre del servidor
     *
     * @return Entrega el valor del nombe del servidor
     * @since empty
     */
    public String getServer() {
        return this.server;
    }

    /**
     * Mï¿½todo que Permite iniciar todos los servicios del UCI
     *
     * @param config Valor del cobjeto de configuraciï¿½n
     * @since empty
     * @throws ServletException Error
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        try {
            this.logger = UCILogger.getInstance();
            HbSession.getInstance(true, config.getServletContext().getRealPath("") + "/WEB-INF/lib");

            Parameters param = Parameters.getInstance();

            this.server = param.getValue("uci.server.name");
            Thread.currentThread().setName("UCIInit " + this.server);
            sServer = this.server;
            Sender.server = this.server;
            this.logger.info("Se inicia el servidor UCI " + this.server);
            this.daExit = new DataAccess();
            this.initAll();
        } catch (Exception e) {
            this.logger.severe("Error al iniciar el Servidor: " + e.getMessage());
            this.logger.throwing(e);
            throw new ServletException(e.getMessage(), e);
        } finally {
            if (this.daExit != null) {
                this.daExit.close();
            }
        }
    }

    /**
     * M�todo que Permite iniciar todos los servicios asociados al servidor
     *
     * @since empty
     * @throws Exception Error
     */
    @SuppressWarnings(UNCHECKED)
    public void initAll() throws Exception {
        if (this.channels != null) {
            try {
                this.stopAll();
            } catch (Exception e1) {
                this.logger.throwing(e1);
            }
        }

        DataAccess da = this.getDaExit();

        this.onLine(da);
        this.empty = new ArrayList<String>();
        this.channels = this.getChannels(da);

        Iterator it = this.channels.keySet().iterator();

        while (it.hasNext()) {
            this.initServicesByChannel((String) it.next());
        }

        int n = this.empty.size();

        for (int i = 0; i < n; i++) {
            this.channels.remove(this.empty.get(i));
        }

        this.empty.clear();
        this.logger.info("Inicializacion Terminada");

    }

    /**
     * M�todo que Permite iniciar un canal
     *
     * @param pChannel Valor del nombre del canal
     * @since empty
     * @throws Exception Error
     */
    public void initChannel(String pChannel) throws Exception {
        if (this.channels.containsKey(pChannel)) {
            throw new UCIException("UCI-0031", "El Canal " + pChannel + " ya se encuentra iniciado");
        }

        DataAccess da = this.getDaExit();

        Tchannels channel = da.getChannel(pChannel);
        Channel ch = new Channel(channel, this.server);

        ch.setProvider(this);
        this.channels.put(pChannel, ch);
        ch.startup();
    }

    /**
     * Mï¿½todo que Permite iniciar los servicios por canal
     *
     * @param pChannel Valor del nombre del canal
     * @since empty
     * @throws Exception Error
     */
    private void initServicesByChannel(String pChannel) throws Exception {
        this.logger.info("Se inicia el Canal " + pChannel);

        Channel channel = this.channels.get(pChannel);

        channel.startup();
        if (channel.getServicesCount() < 1) {
            this.empty.add(pChannel);
            this.logger.warning("El Canal " + pChannel + " no posee dispositivos");
        } else {
            this.logger.info("El Canal " + pChannel + " esta operativo");
        }
    }

    /**
     * Mï¿½todo que Permite listar los canales
     *
     * @return Entrega un listado de canales
     * @since empty
     * @throws Exception Error
     */
    public List<Map<String, String>> listChannels() throws Exception {
        List<Map<String, String>> l = new ArrayList<Map<String, String>>();
        Iterator<String> it = this.channels.keySet().iterator();
        List<String> sorted = new ArrayList<String>();

        while (it.hasNext()) {
            sorted.add(it.next());
        }

        Collections.sort(sorted);

        for (int i = 0; i < sorted.size(); i++) {
            String key = sorted.get(i);
            Channel channel = this.channels.get(key);
            Map<String, String> m = new HashMap<String, String>();

            m.put(Constant.CHANNEL_NAME, key);
            m.put(Constant.CHANNEL_DESCRIPTION, channel.getDescription());
            m.put(Constant.CHANNEL_STATUS, "" + channel.isStarted());
            l.add(m);
        }

        return l;
    }

    /**
     * Mï¿½todo que Permite listar los controladores pertenecientes a un canal
     *
     * @param pChannel Valor del Canal
     * @return Entrega el valor de lasta de controladores
     * @since empty
     * @throws Exception Error
     * @deprecated Este metodo no hace nada
     */
    @Deprecated
    @SuppressWarnings(UNCHECKED)
    public List listControllers(String pChannel) throws Exception {
        List<Map<String, Object>> controllerList = new ArrayList<Map<String, Object>>();

        Object[] params = new Object[2];

        params[0] = pChannel;
        params[1] = this.getServer();

        List dataTable = new ArrayList();

        int n = dataTable.size();

        for (int i = 0; i < n; i++) {
            String clase = (String) ((List) dataTable.get(i)).get(1);
            Map<String, Object> m = new HashMap<String, Object>();

            m.put(Constant.CODE, ((List) dataTable.get(i)).get(0));
            m.put(Constant.DESCRIPTION,
                            ((Controlador) Class.forName(clase, true, this.getClass().getClassLoader()).newInstance()).getDescription());
            controllerList.add(m);
        }

        return controllerList;
    }

    /**
     * Mï¿½todo que Permite listar los canales desactivados
     *
     * @return Entrega el valor de la lista de canales inactivos
     * @since empty
     * @throws Exception Error
     * @deprecated Este metodo no hace nada
     */
    @Deprecated
    @SuppressWarnings(UNCHECKED)
    public List listDisabledChannels() throws Exception {
        List dataTable = new ArrayList();

        Object[] params = new Object[1];

        params[0] = this.getServer();

        Iterator<String> it = this.channels.keySet().iterator();
        List<String> sorted = new ArrayList<String>();

        while (it.hasNext()) {
            sorted.add(it.next());
        }

        Collections.sort(sorted);

        for (int i = 0; i < sorted.size(); i++) {
            String key = sorted.get(i);

            for (int j = 0; j < dataTable.size(); j++) {
                String code = (String) ((List) dataTable.get(j)).get(0);

                if (code.compareTo(key) == 0) {
                    dataTable.remove(j);
                }
            }
        }

        return dataTable;
    }

    /**
     * Mï¿½todo que Permite listar los dispositivos desactivados
     *
     * @param channel Valor de
     * @return Entrega el valor de la lista de dispositivos inactivos
     * @since empty
     * @throws Exception Error
     */
    @SuppressWarnings(UNCHECKED)
    public List listDisabledDevices(String channel) throws Exception {
        DataAccess da = this.getDaExit();

        Channel ch = this.channels.get(channel);

        List ondevices = ch.getServices();

        List offdevices = offdevices = da.getServiceList(channel, this.server);

        for (int i = 0; i < ondevices.size(); i++) {
            String ondevice = ((Service) ondevices.get(i)).getDispositivo();

            for (int j = 0; j < offdevices.size(); j++) {
                Map mdev = (Map) offdevices.get(j);
                String offdevice = (String) mdev.get(Constant.SERVICE_NAME);

                if (ondevice.compareTo(offdevice) == 0) {
                    offdevices.remove(j);

                    break;
                }
            }
        }

        return offdevices;
    }

    /**
     * Mï¿½todo que Permite listar los servicios por canal
     *
     * @param pChannel Valor del Canal
     * @param mParam Valor de los parï¿½metros de bï¿½squeda
     * @return Entrega el valor de la lista de servicios
     * @since empty
     * @throws Exception Error
     */
    @SuppressWarnings(UNCHECKED)
    public List<Map<String, Object>> listServicesByChannel(String pChannel, Map mParam) throws Exception {
        List<Map<String, Object>> l = new ArrayList<Map<String, Object>>();
        String device = (String) mParam.get(Constant.DEVICE);
        String connector = (String) mParam.get(Constant.CONNECTOR);
        String estatus = (String) mParam.get(Constant.STATUS);
        Channel channel = this.channels.get(pChannel);

        List<Service> services = channel.getServices();
        Iterator<Service> it = services.iterator();
        List<Service> sorted = new ArrayList<Service>();

        while (it.hasNext()) {
            sorted.add(it.next());
        }

        Collections.sort(sorted);

        for (int i = 0; i < sorted.size(); i++) {
            Service ser = sorted.get(i);
            Controlador con = ser.getConnector();
            Map<String, Object> m = new HashMap<String, Object>();

            m.put(Constant.CHANNEL, con.getChannel());
            m.put(Constant.SERVICE_NAME, con.getConnectorName());
            m.put(Constant.SERVICE_DESCRIPTION, con.getDescription());
            m.put(Constant.SERVICE_STATUS, "" + con.isStarted());
            m.put(Constant.SERVICE_CONNECT, "" + con.isConnected());
            if (this.serviceFilter(device, connector, estatus, con)) {
                continue;
            }

            l.add(m);
        }

        return l;
    }

    /**
     * M�todo que Permite poner fuera de servicio al servidor
     *
     * @since empty
     * @throws Exception Error
     */
    private void offLine() throws Exception {
        this.daExit.setServerStatus("0", this.server);
    }

    /**
     * M�todo que Permite por en l�nea al servidor
     *
     * @param pDa Valor de
     * @since empty
     * @throws Exception Error
     */
    private void onLine(DataAccess pDa) throws Exception {
        pDa.setServerStatus("1", this.server);
    }

    /**
     * Mï¿½todo que valida que se reciba el canal
     *
     * @param pChannel Valor del canal
     * @since empty
     * @throws Exception Error
     */
    public void requiredChannel(String pChannel) throws Exception {
        if (pChannel == null) {
            throw new UCIException("UCI-0032", REQUIRED_CHANNEL);
        }

        Object chan = this.channels.get(pChannel);

        if (chan == null) {
            throw new UCIException("UCI-0033", MessageFormat.format(UNFOUNDCHANNEL, new Object[] { pChannel }));
        }
    }

    /**
     * M�todo que Permite enviar los mensajes llegados a cada dispositivo
     *
     * @param pMessage Valor del mensaje
     * @param pChannel Valor del canal
     * @param pDispositivo Valor del dispositivo
     * @param pProperties Valor de las propiedades de envï¿½o
     * @since empty
     * @throws Exception Error
     */
    public void sendMessage(Serializable pMessage, String pChannel, String pDispositivo, Properties pProperties)
        throws Exception {
        try {
            this.logger.info("Mensaje para el canal " + pChannel + " Dispositivo " + pDispositivo);
            this.requiredChannel(pChannel);

            Channel channel = this.channels.get(pChannel);
            Service ser = channel.getService(pDispositivo);
            Controlador con = ser.getConnector();

            con.sendMessage(pMessage, pProperties);
        } catch (Exception e) {
            this.logger.info("Error al enviar el Mensaje " + e.getMessage());
            throw e;
        }
    }

    /**
     * M�todo que Permite obener la informaci�n de servicios
     *
     * @param pChannel Valor del canal
     * @param pKey Valor del nombre del Servicio
     * @return Entrega un map con la informaciï¿½n del Servicio
     * @since empty
     * @throws Exception Error
     */
    public Map<String, Object> service(String pChannel, String pKey) throws Exception {
        Channel channel = this.channels.get(pChannel);
        Service ser = channel.getService(pKey);
        Controlador con = ser.getConnector();
        Map<String, Object> m = new HashMap<String, Object>();

        m.put(Constant.SERVICE_NAME, con.getConnectorName());
        m.put(Constant.SERVICE_DESCRIPTION, con.getDescription());
        m.put(Constant.SERVICE_STATUS, "" + con.isStarted());
        m.put(Constant.SERVICE_CONNECT, "" + con.isConnected());
        m.put(Constant.SERVICE_INIT_DATE, con.getInitDateString());
        m.put(Constant.SERVICE_ERROR_COUNT, "" + con.getErrorCount());
        m.put(Constant.SERVICE_LAST_INPUT, con.getLastInputMessage());
        m.put(Constant.SERVICE_LAST_INPUT_DATE, con.getLastInputDateString());
        m.put(Constant.SERVICE_INPUT_COUNT, "" + con.getInputCount());
        m.put(Constant.SERVICE_LAST_OUTPUT, con.getLastOutputMessage());
        m.put(Constant.SERVICE_LAST_OUTPUT_DATE, con.getLastOutputDateString());
        m.put(Constant.SERVICE_OUTPUT_COUNT, "" + con.getOutputCount());
        m.put(Constant.SERVICE_PARAMETERS, ser.getParameters());
        m.put(Constant.SERVICE_GROUPING, ser.getGrouping());
        m.put(Constant.SERVICE_AUTOINIT, ser.getInit());
        m.put("service.extra", con.getExtraData());
        return m;
    }

    private boolean serviceFilter(String device, String connector, String estatus, Controlador con) throws Exception {
        if ((device.compareTo("%") != 0) && (con.getConnectorName().indexOf(device) < 0)) {
            //Filtro por conector
            return true;
        }
        if ((connector.compareTo("0") != 0) && (connector.compareTo(con.getDescription()) != 0)) {
            //Filtro por conector
            return true;
        }
        if ((estatus.compareTo("0") != 0) && (estatus.compareTo("" + con.isStarted()) != 0)) {
            //Filtro por estatus
            return true;
        }
        return false;
    }

    /**
     * M�todo que Permite detener todos los servicios
     *
     * @since empty
     * @throws Exception Error
     */
    @SuppressWarnings(UNCHECKED)
    public void stopAll() throws Exception {
        Iterator it = this.channels.keySet().iterator();

        while (it.hasNext()) {
            String sChannel = (String) it.next();

            try {
                Channel ch = this.channels.get(sChannel);

                ch.shutdown();
            } catch (Exception e) {
                this.logger.info("Problemas en la detencion de los servicios del canal " + sChannel + ": " +
                                e.getMessage());
                this.logger.throwing(e);
            }
        }

        this.offLine();
        this.channels.clear();
        this.logger.info("Finalizacion Terminada");
    }
}
