package com.fitbank.bpmserver;

import com.fitbank.common.conectivity.HbSession;
import com.fitbank.common.exception.ExceptionHandler;
import com.fitbank.common.exception.FitbankCommitableException;
import com.fitbank.common.exception.FitbankException;
import com.fitbank.dto.GeneralResponse;
import com.fitbank.dto.management.Detail;
import com.fitbank.common.Helper;
import com.fitbank.common.RequestData;
import com.fitbank.processor.helper.ProcessorHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.commons.configuration.Configuration;

/**
 * Servidor del core de fitbank | Recibe pedidos por un socket y los envía a
 * procesar.
 * 
 * @author Bantec Inc.
 */
public class FitBpmServerSocket extends ServiceBpm {

    private static final Configuration CONFIG = FitBpmServerParam.getConfig();

    private final int port;

    private final ServerSocket server;

    private Detail requestDetail = new Detail();

    protected static final ProcessorHelper PROCESSOR_HELPER = ProcessorHelper
            .getInstance();

    public FitBpmServerSocket() throws IOException {
        this.port = CONFIG.getInt("fitbpmserver.port");
        this.server = new ServerSocket(this.port);
        this.setName("FitServerSocket");

        LOGGER.debug("Socket de Servicio FIT Levantado");
    }

    public int getPort() {
        return port;
    }

    @Override
    public void execute() {
        try {
            new SocketThread(this.server.accept()).start();
        } catch (IOException ex) {
            LOGGER.error("No se pudo iniciar la conexión", ex);
        }
    }

    @Override
    public void shutdown() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private class SocketThread extends Thread {

        private final Socket client;
        private InputStream in;
        private ObjectInputStream oin;
        private OutputStream out;
        private ObjectOutputStream oout;

        public SocketThread(Socket client) {
            this.client = client;
        }

        @Override
        public void run() {
            try {
                this.client.setSoLinger(true, 0);
                this.client.setSoTimeout(100 * 1000);

                this.in = client.getInputStream();
                this.oin = new ObjectInputStream(in);
                this.out = client.getOutputStream();
                this.oout = new ObjectOutputStream(out);
                requestDetail = (Detail) oin.readObject();
                RequestData.setSession(requestDetail);
                RequestData.setOrigin(requestDetail);
                BpmTransaction bpmtransaction = new BpmTransaction();
                try {
                    Helper.setSession(HbSession.getInstance().openSession());
                    Helper.beginTransaction();
                    requestDetail = bpmtransaction.executeNormal(requestDetail);
                    Helper.commitTransaction();
                } catch (Throwable ex) {
                    exceptionHandler(ex);
                }
                oout.writeObject(requestDetail);
                oin.readObject();
            } catch (ClassNotFoundException ex) {
                LOGGER.error(ex);
            } catch (IOException ex) {
                LOGGER.error(ex);
            } catch (Exception e) {
                LOGGER.error(e);
            } finally {
                close();
            }
        }

        private void close() {
            try {
                Helper.closeSession();
                oout.close();
                out.close();
            } catch (IOException ex) {
                LOGGER.warn("No se pudo cerrar el canal de comunicación", ex);
            }

            try {
                oin.close();
                in.close();
            } catch (IOException ex) {
                LOGGER.warn("No se pudo cerrar el canal de comunicación", ex);
            }

            try {
                client.close();
            } catch (IOException ex) {
                LOGGER.warn("No se pudo cerrar el canal de comunicación", ex);
            }
        }

    }

    /**
     * Manejo de Excepciones
     * 
     * @param e
     *            Excepcion
     */
    private void exceptionHandler(Throwable e) {
        LOGGER.warn("Se responderá con el mensaje original");
        this.manageCommitableException(e);
        try {
            boolean eval = true;
            if ((e instanceof FitbankException)
                    && (((FitbankException) e).getCode()
                            .compareTo("SIMULACION") == 0)) {
                eval = false;
            }
            if (eval) {
                ExceptionHandler eh = new ExceptionHandler(e,
                        ((this.requestDetail == null) || (this.requestDetail
                                .getLanguage() == null)) ? "es"
                                : this.requestDetail.getLanguage());
                GeneralResponse resp = eh.manage();
                this.requestDetail.setResponse(resp);
            } else {
                this.requestDetail.setResponse(this.getOk());
            }
        } catch (Exception e1) {
            LOGGER.error(e);
            LOGGER.error(e1);
        }
    }

    /**
     * Determina si la excepción proviene de la clase FitbankCommitableException
     * para hacer rollback
     * 
     * @param e
     */
    private void manageCommitableException(Throwable e) {
        try {
            Throwable exc = e.getCause();
            if (!((e instanceof FitbankCommitableException) || (exc instanceof FitbankCommitableException))) {
                Helper.rollbackTransaction();
                Helper.beginTransaction();
            }
        } catch (Exception ex) {
        }
    }

    private GeneralResponse getOk() {
        GeneralResponse gr = this.requestDetail.getResponse();
        if (this.requestDetail.getResponse() == null) {
            gr = new GeneralResponse(GeneralResponse.OK);
        }
        if (gr.getCode().compareTo(GeneralResponse.OK) == 0) {
            gr.setUserMessage(this.getOKMessage());
        }
        return gr;
    }

    /**
     * Devuelve el mensaje ok.
     * 
     * @return
     */
    private String getOKMessage() {
        try {
            ResourceBundle res = ResourceBundle.getBundle("messages",
                    new Locale(this.requestDetail.getLanguage().toLowerCase()));
            return res.getString("M0");
        } catch (Exception e) {
            return "TRANSACCION REALIZADA CORRECTAMENTE";
        }
    }
}
