package org.itsnat.droid.impl.browser;

import android.os.AsyncTask;

import org.apache.http.params.HttpParams;
import org.itsnat.droid.ClientErrorMode;
import org.itsnat.droid.GenericHttpClient;
import org.itsnat.droid.HttpRequestResult;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.ItsNatDroidServerResponseException;
import org.itsnat.droid.OnHttpRequestErrorListener;
import org.itsnat.droid.OnHttpRequestListener;
import org.itsnat.droid.impl.util.NameValue;
import org.itsnat.droid.impl.util.StringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by jmarranz on 9/10/14.
 */
public class GenericHttpClientImpl extends GenericHttpClientBaseImpl implements GenericHttpClient
{
    protected String userUrl;
    protected String method = "POST"; // Por defecto
    protected RequestPropertyMap requestPropertyMap;
    protected HttpParams httpParams;
    protected int connectTimeout;
    protected int readTimeout;
    protected List<NameValue> paramList = new ArrayList<NameValue>(10);
    protected String overrideMime;
    protected int errorMode = ClientErrorMode.SHOW_SERVER_AND_CLIENT_ERRORS;

    public GenericHttpClientImpl(ItsNatDocImpl itsNatDoc)
    {
        super(itsNatDoc);
        PageImpl page = itsNatDoc.getPageImpl();
        this.requestPropertyMap = page.getRequestPropertyMapImpl().copy();
        this.httpParams = page.getHttpParams().copy();
        this.connectTimeout = page.getConnectTimeout();
        this.readTimeout = page.getReadTimeout();
    }

    @Override
    public GenericHttpClient setClientErrorMode(int errorMode)
    {
        setClientErrorModeNotFluid(errorMode);
        return this;
    }

    public int getClientErrorMode()
    {
        return errorMode;
    }

    public void setClientErrorModeNotFluid(int errorMode)
    {
        // if (errorMode == ClientErrorMode.NOT_CATCH_ERRORS) throw new ItsNatDroidException("ClientErrorMode.NOT_CATCH_ERRORS is not supported"); // No tiene mucho sentido porque el objetivo es dejar fallar y si el usuario no ha registrado "error listeners" ItsNat Droid deja siempre fallar lanzando la excepción
        this.errorMode = errorMode;
    }

    @Override
    public GenericHttpClient setOnHttpRequestListener(OnHttpRequestListener listener)
    {
        setOnHttpRequestListenerNotFluid(listener);
        return this;
    }

    @Override
    public GenericHttpClient setOnHttpRequestErrorListener(OnHttpRequestErrorListener httpErrorListener)
    {
        setOnHttpRequestErrorListenerNotFluid(httpErrorListener);
        return this;
    }

    @Override
    public GenericHttpClient setRequestMethod(String method)
    {
        setMethodNotFluid(method);
        return this;
    }

    public void setMethodNotFluid(String method)
    {
        this.method = method;
    }

    @Override
    public GenericHttpClient setURL(String url)
    {
        setURLNotFluid(url);
        return this;
    }

    private void setURLNotFluid(String url)
    {
        this.userUrl = url;
    }


    protected String getFinalURL()
    {
        // En el ejemplo ItsNatDroidServletNoItsNat se hace un ejemplo de llamada a GenericHttpClient con código custom en donde no se especifica un userUrl por lo que se usa el pageURLBase de contexto
        // que en ese caso es el path al servlet ItsNatDroidServletNoItsNat, de esa manera nos evitamos formar un URL y las llamadas específicas las hacemos a través de los parámetros
        // Podría ser también un path a un JSP (a fin de cuentas un JSP es un servlet).

        return StringUtil.isEmpty(userUrl) ? itsNatDoc.getPageImpl().getPageURLBase() : userUrl; // Como se puede ver seguridad de "single server" ninguna
    }

    public void addParamNotFluid(String name,Object value)
    {
        // Si se añade el mismo parámetro varias veces, es el caso de multivalor
        paramList.add(new NameValue(name, value));
    }

    public void clearParamsNotFluid()
    {
        paramList.clear();
    }

    @Override
    public GenericHttpClient addParameter(String name, Object value)
    {
        addParamNotFluid(name, value);
        return this;
    }

    @Override
    public GenericHttpClient clearParameters()
    {
        clearParamsNotFluid();
        return this;
    }

    public RequestPropertyMap getRequestPropertyMap()
    {
        return requestPropertyMap;
    }

    @Override
    public GenericHttpClient addRequestProperty(String name, String value)
    {
        requestPropertyMap.addProperty(name, value);
        return this;
    }

    @Override
    public GenericHttpClient setRequestProperty(String name, String value)
    {
        requestPropertyMap.setProperty(name, value);
        return this;
    }

    @Override
    public boolean removeProperty(String name)
    {
        return requestPropertyMap.removeProperty(name);
    }

    @Override
    public String getRequestProperty(String name)
    {
        return requestPropertyMap.getPropertySingle(name);
    }

    @Override
    public Map<String, List<String>> getRequestProperties()
    {
        return requestPropertyMap.getPropertyUnmodifiableMap();
    }

    public HttpParams getHttpParams()
    {
        return httpParams;
    }

    @Override
    public GenericHttpClient setConnectTimeout(int timeoutMillis)
    {
        this.connectTimeout = timeoutMillis;
        return this;
    }

    @Override
    public int getConnectTimeout()
    {
        return connectTimeout;
    }

    @Override
    public GenericHttpClient setReadTimeout(int timeoutMillis)
    {
        this.readTimeout = timeoutMillis;
        return this;
    }

    @Override
    public int getReadTimeout()
    {
        return readTimeout;
    }


    public void setOverrideMimeTypeNotFluid(String mime)
    {
        this.overrideMime = mime;
    }

    @Override
    public GenericHttpClient setOverrideMimeType(String mime)
    {
        setOverrideMimeTypeNotFluid(mime);
        return this;
    }

    public void request(boolean async) // Interno
    {
        if (async)
            requestAsync();
        else
            requestSync();
    }

    @Override
    public HttpRequestResult requestSync()
    {
        String url = getFinalURL();
        HttpRequestData httpRequestData = new HttpRequestData(this);
        List<NameValue> paramList = this.paramList; // No hace falta clonar pues es síncrono

        HttpRequestResultOKImpl result;
        try
        {
            result = executeInBackground(this,method, url, httpRequestData, paramList, overrideMime);
        }
        catch (Exception ex)
        {
            int errorMode = getClientErrorMode();
            onFinishError(this,ex, httpRequestListener,errorListener,errorMode);

            return null; // No podemos seguir
        }

        onFinishOk(this, result, httpRequestListener, errorListener);

        return result;
    }


    @Override
    public void requestAsync()
    {
        String url = getFinalURL();
        HttpRequestData httpRequestData = new HttpRequestData(this);
        List<NameValue> paramList = new ArrayList<NameValue>(this.paramList); // hace una copia, los NameValue son de sólo lectura por lo que no hay problema de compartirlos en hilos
        GenericHttpClientAsyncTask task = new GenericHttpClientAsyncTask(this,method,url,httpRequestData, paramList, httpRequestListener,errorListener,overrideMime);
        task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // Con execute() a secas se ejecuta en un "pool" de un sólo hilo sin verdadero paralelismo
    }

    protected static HttpRequestResultOKImpl executeInBackground(GenericHttpClientImpl parent,String method,String url,HttpRequestData httpRequestData,List<NameValue> paramList,String overrideMime) throws Exception
    {
        // Multihilo en el caso async
        return HttpUtil.httpAction(method,url, httpRequestData, paramList, overrideMime);
    }

    public static void onFinishOk(GenericHttpClientImpl parent,HttpRequestResultOKImpl result,OnHttpRequestListener listener,OnHttpRequestErrorListener errorListener)
    {
        try
        {
            parent.processResult(result,listener);
        }
        catch(Exception ex)
        {
            if (errorListener != null)
            {
                HttpRequestResult resultError = (ex instanceof ItsNatDroidServerResponseException) ? ((ItsNatDroidServerResponseException)ex).getHttpRequestResult() : result;
                errorListener.onError(parent.getPageImpl(), ex, resultError);
            }
            else
            {
                if (ex instanceof ItsNatDroidException) throw (ItsNatDroidException)ex;
                else throw new ItsNatDroidException(ex);
            }
        }
    }


    public static void onFinishError(GenericHttpClientImpl parent,Exception ex,OnHttpRequestListener listener,OnHttpRequestErrorListener errorListener,int errorMode)
    {
        HttpRequestResult result = (ex instanceof ItsNatDroidServerResponseException) ?  ((ItsNatDroidServerResponseException)ex).getHttpRequestResult() : null;

        ItsNatDroidException exFinal = GenericHttpClientBaseImpl.convertException(ex);

        if (errorListener != null)
        {
            errorListener.onError(parent.getPageImpl(),exFinal, result);
        }
        else
        {
            if (errorMode != ClientErrorMode.NOT_CATCH_ERRORS)
            {
                // Error del servidor, lo normal es que haya lanzado una excepción
                ItsNatDocImpl itsNatDoc = parent.getItsNatDocImpl();
                itsNatDoc.showErrorMessage(true, result,exFinal, errorMode);
            }
            else throw exFinal;
        }
    }
}
