/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.core;

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

import org.apache.http.protocol.HTTP;
import android.util.Log;
import android.util.Config;
import android.net.http.*;

/**
 * Implements EventHandler and provides test functionality to validate
 * responses to requests from the test server
 */
public class TestEventHandler implements EventHandler {

    /**
     * Status variables
     */
    private int majorVersion = -1;
    private int minorVersion = -1;
    private int responseCode = -1;
    private String reasonPhrase;

    /* List of headers received */
    private Map<String, String> headerMap;

    /* Used to sync low level delayed requests */
    public static final Object syncObj = new Object();

    /* Indicates whether the low level request testing is in operation */
    private boolean useLowLevel = false;

    /* Indicates whether responses should be automatically generated or
     * delayed
     */
    private boolean delayResponse = false;

    /* Test method expectation identifiers */
    public final static int TEST_REQUEST_SENT = 0;
    public final static int TEST_STATUS = 1;
    public final static int TEST_HEADERS = 2;
    public final static int TEST_LOCATION_CHANGED = 3;
    public final static int TEST_DATA = 4;
    public final static int TEST_ENDDATA = 5;
    public final static int TEST_ERROR = 6;
    public final static int TEST_SSL_CERTIFICATE_ERROR = 7;

    public final static int TEST_NUM_EXPECTS = 8;

    /* Expected status codes */
    private int expectMajor = -1;
    private int expectMinor = -1;
    private int expectCode = -1;

    /* Array indicating which event types are expected */
    private boolean[] expects = new boolean[TEST_NUM_EXPECTS];

    /* Array indicating which event types are not expected */
    private boolean[] notExpecting = new boolean[TEST_NUM_EXPECTS];

    /* Indicates which events have been received */
    private boolean[] eventsReceived = new boolean[TEST_NUM_EXPECTS];

    /* Redirection variables */
    private String expectLocation;
    private int expectPermanent = -1;

    /* Content data expected to be received */
    private byte[] expectData;
    private int expectDataLength = -1;

    private int expectErrorId = -1;

    private int expectSslErrors = -1;
    private SslCertificate expectCertificate;

    public class TestHeader {
        public TestHeader(String n, String v) {
            name = n;
            value = v;
        }
        public String name;
        public String value;
    }
    
    private ArrayList<TestHeader> expectHeaders = new ArrayList<TestHeader>();

    /* Holds failure details */
    private StringBuffer expectDetails = new StringBuffer();

    /* If we use a request handle, we retain a reference here for redirects
     * using setupRedirect
     */
    private RequestHandle mRequestHandle;

    /* The low level API uses this reference also for non-delayed requests */
    private LowLevelNetRunner netRunner;

    public TestEventHandler() {
        for (int i = 0; i < TEST_NUM_EXPECTS; i++) {
            expects[i] = false;
            notExpecting[i] = false;
            eventsReceived[i] = false;
        }
    }

    /**
     * Implementation of EventHandler method called when a request has been
     * sent. If the test is waiting for this call, it will be signalled,
     * otherwise this method will trigger the response to be read
     * automatically.
     */
    public void requestSent() {
      Log.v(LOGTAG, "TestEventHandler:requestSent()");
      expects[TEST_REQUEST_SENT] = false;
      eventsReceived[TEST_REQUEST_SENT] = true;
      if (notExpecting[TEST_REQUEST_SENT]) {
          expectDetails.append("Request sent event received but not expected");
          expectDetails.append("\r\n");
      }

      if (useLowLevel) {
        if (delayResponse) {
          synchronized (syncObj) {
            syncObj.notifyAll();
          }
        } else {
            // mRequest.startReadingResponse();
        }
      }
    }

    /**
     * Implements the EventHandler status method called when a server status
     * response is received.
     * @param major_version The HTTP major version
     * @param minor_version The HTTP minor version
     * @param code The status code
     * @param reason_phrase A reason phrase passed to us by the server
     */
    public void status(int major_version, int minor_version,
        int code, String reason_phrase) {
      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler:status() major: " + major_version +
            " minor: " + minor_version +
            " code: " + code +
            " reason: " + reason_phrase);
      }

      eventsReceived[TEST_STATUS] = true;
      if (notExpecting[TEST_STATUS]) {
        expectDetails.append("Status event received but not expected");
        expectDetails.append("\r\n");
      }

      majorVersion = major_version;
      minorVersion = minor_version;
      responseCode = code;
      reasonPhrase = reason_phrase;

      if (expectMajor != -1) {
        if (expectMajor == major_version) {
          expectMajor = -1;
        } else {
          expectDetails.append("Major version expected:"+expectMajor+
              " got:"+major_version);
          expectDetails.append("\r\n");
        }
      }

      if (expectMinor != -1) {
        if (expectMinor == minor_version) {
          expectMinor = -1;
        } else {
          expectDetails.append("Minor version expected:"+expectMinor+
              " got:"+minor_version);
          expectDetails.append("\r\n");
        }
      }

      if (expectCode != -1) {
        if (expectCode == code) {
          expectCode = -1;
        } else {
          expectDetails.append("Status code expected:"+expectCode+
              " got:"+code);
          expectDetails.append("\r\n");
        }
      }


      if ((expectMajor == -1) && (expectMinor == -1) && (expectCode == -1)) {
        expects[TEST_STATUS] = false;
      } else {
        System.out.println("MAJOR = "+expectMajor+" MINOR = "+expectMinor+
            " CODE = "+expectCode);
      }
    }

    /**
     * Implements the EventHandler headers method called when a server
     * sends header fields
     */
    public void headers(Headers headers) {
        if (Config.LOGV) {
            Log.v(LOGTAG, "TestEventHandler:headers()");
        }
        expects[TEST_HEADERS] = false;

        if (notExpecting[TEST_HEADERS]) {
            expectDetails.append("Header event received but not expected");
            expectDetails.append("\r\n");
        }

        /* Check through headers received for matches with expected
         * headers */
        if (expectHeaders.isEmpty()) {
            return;
        }      
        
        for (int i = expectHeaders.size() - 1; i >= 0; i--) {
            TestHeader h =  expectHeaders.get(i);
            System.out.println("Expected header name: " + h.name);
            String s = null;
            switch (h.name.hashCode()) {
            case -1132779846:
                s = Long.toString(headers.getContentLength());
                break;
            case 785670158:
                s = headers.getContentType();
                break;
            case 2095084583:
                s = headers.getContentEncoding();
                break;
            case 1901043637:
                s = headers.getLocation();
                break;
            case -243037365:
                s = headers.getWwwAuthenticate();
                break;
            case -301767724:
                s = headers.getProxyAuthenticate();
                break;
            case -1267267485:
                s = headers.getContentDisposition();
                break;
            case 1397189435:
                s = headers.getAcceptRanges();
                break;
            case -1309235404:
                s = headers.getExpires();
                break;
            case -208775662:
                s = headers.getCacheControl();
                break;
            case 150043680:
                s = headers.getLastModified();
                break;
            case 3123477:
                s = headers.getEtag();
                break;
            case -775651618:
                int ct = headers.getConnectionType();
                if (ct == Headers.CONN_CLOSE) {
                    s = HTTP.CONN_CLOSE;
                } else if (ct == Headers.CONN_KEEP_ALIVE) {
                    s = HTTP.CONN_KEEP_ALIVE;
                }
                break;
            default:
                s = null;
                
            }
            if (evaluateHeader(h, s)) {
                expectHeaders.remove(i);
            }
        }
            
    }

    public boolean evaluateHeader(TestHeader h, String value) {
        if (value == null) {
            expects[TEST_HEADERS] = true;
            System.out.print(" Missing!  ");
            expectDetails.append(" missing header " + h.name);
            return false;
        }
        if (h.value == null) {
            System.out.println("Expect value = null");
            return true;
        }
        System.out.println("Expect value = " +
                (h.value.toLowerCase()) + " got " +
                value.toLowerCase());
        
        if (!h.value.equalsIgnoreCase(value)) {
            expectDetails.append("expect header value " + h.value +
                    " got " + value);
            expects[TEST_HEADERS] = true;
            return false;
        }
        return true;
    }
    /**
     * Implements the EventHandler locationChanged method called when a server
     * sends a redirect message
     * @param newLocation The URL to the new server
     * @param permanent Indicator of whether this is a permanent change
     */
    public void locationChanged(String newLocation, boolean permanent) {
      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler: locationChanged() " +
            newLocation + " permanent " + permanent);
      }

      eventsReceived[TEST_LOCATION_CHANGED] = true;
      if (notExpecting[TEST_LOCATION_CHANGED]) {
        expectDetails.append("Location changed event received but "+
            "not expected");
        expectDetails.append("\r\n");
      }

      if (expectLocation != null) {
        if (expectLocation.equals(newLocation)) {
          expectLocation = null;
        } else {
          expectDetails.append("Location expected:"+expectLocation+
              " got:"+newLocation);
          expectDetails.append("\r\n");
        }
      }

      if (expectPermanent != -1) {
        if (((expectPermanent == 0) && !permanent) ||
            ((expectPermanent == 1) && permanent)){
          expectPermanent = -1;
        } else {
          expectDetails.append("Location permanent expected:"+
              expectPermanent+" got"+permanent);
          expectDetails.append("\r\n");
        }
      }

      if ((expectLocation == null) && (expectPermanent == -1))
        expects[TEST_LOCATION_CHANGED] = false;
    }

    /**
     * Implements the EventHandler data method called when a server
     * sends content data
     * @param data The byte array content
     * @param len The length of the data
     */
    public void data(byte[] data, int len) {
      boolean mismatch = false;

      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler: data() " + len + " bytes");
      }

      eventsReceived[TEST_DATA] = true;
      if (notExpecting[TEST_DATA]) {
        expectDetails.append("Data event received but not expected");
        expectDetails.append("\r\n");
      }

      Log.v(LOGTAG, new String(data, 0, len));

      if (expectDataLength != -1) {
        if (expectDataLength == len) {
          expectDataLength = -1;
        } else {
          expectDetails.append("expect data length mismatch expected:"+
              expectDataLength+" got:"+len);
          expectDetails.append("\r\n");
        }

        /* Check data only if length is the same */
        if ((expectDataLength == -1) && expectData != null) {
          for (int i = 0; i < len; i++) {
            if (expectData[i] != data[i]) {
              mismatch = true;
              expectDetails.append("Expect data mismatch at byte "+
                  i+" expected:"+expectData[i]+" got:"+data[i]);
              expectDetails.append("\r\n");
              break;
            }
          }
        }
      }

      if ((expectDataLength == -1) || !mismatch)
        expects[TEST_DATA] = false;
    }

    /**
     * Implements the EventHandler endData method called to
     * indicate completion or a request
     */
    public void endData() {
      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler: endData() called");
      }

      eventsReceived[TEST_ENDDATA] = true;
      if (notExpecting[TEST_ENDDATA]) {
        expectDetails.append("End data event received but not expected");
        expectDetails.append("\r\n");
      }

      expects[TEST_ENDDATA] = false;

      if (useLowLevel) {
        if (delayResponse) {
          synchronized (syncObj) {
            syncObj.notifyAll();
          }
        } else {
          if (netRunner != null) {
            System.out.println("TestEventHandler: endData() stopping "+
                netRunner);
            netRunner.decrementRunCount();
          }
        }
      }
    }

    /**
     * Implements the EventHandler certificate method called every
     * time a resource is loaded via a secure connection
     */
    public void certificate(SslCertificate certificate) {}

    /**
     * Implements the EventHandler error method called when a server
     * sends header fields
     * @param id Status code of the error
     * @param description Brief description of the error
     */
    public void error(int id, String description) {
      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler: error() called Id:" + id +
            " description " + description);
      }

      eventsReceived[TEST_ERROR] = true;
      if (notExpecting[TEST_ERROR]) {
        expectDetails.append("Error event received but not expected");
        expectDetails.append("\r\n");
      }
      if (expectErrorId != -1) {
        if (expectErrorId == id) {
          expectErrorId = -1;
        } else {
          expectDetails.append("Error Id expected:"+expectErrorId+
              " got:"+id);
          expectDetails.append("\r\n");
        }
      }

      if (expectErrorId == -1)
        expects[TEST_ERROR] = false;

      if (useLowLevel) {
        if (delayResponse) {
          synchronized (syncObj) {
            syncObj.notifyAll();
          }
        } else {
          if (netRunner != null) {
            System.out.println("TestEventHandler: endData() stopping "+
                netRunner);
            netRunner.decrementRunCount();
          }
        }
      }
    }

    /**
     * SSL certificate error callback. Handles SSL error(s) on the way
     * up to the user.
     */
    public boolean handleSslErrorRequest(SslError error) {
      int primaryError = error.getPrimaryError();

      if (Config.LOGV) {
        Log.v(LOGTAG, "TestEventHandler: handleSslErrorRequest(): "+
              " primary error:" + primaryError +
              " certificate: " + error.getCertificate());
      }

      eventsReceived[TEST_SSL_CERTIFICATE_ERROR] = true;
      if (notExpecting[TEST_SSL_CERTIFICATE_ERROR]) {
        expectDetails.append("SSL Certificate error event received "+
            "but not expected");
        expectDetails.append("\r\n");
      }

      if (expectSslErrors != -1) {
        if (expectSslErrors == primaryError) {
            expectSslErrors = -1;
        } else {
            expectDetails.append("SslCertificateError id expected:"+
                expectSslErrors+" got: " + primaryError);
            expectDetails.append("\r\n");
        }
      }

      // SslCertificate match here?

      if (expectSslErrors == -1) // && expectSslCertificate == certificate?
        expects[TEST_SSL_CERTIFICATE_ERROR] = false;

      // return false so that we won't block the thread
      return false;
    }

    /**
     * Use the low level net runner with no delayed response
     * @param runner The LowLevelNetRunner object
     */
    public void setNetRunner(LowLevelNetRunner runner) {
      setNetRunner(runner, false);
    }

    /**
     * Use the low level net runner and specify if the response
     * should be delayed
     * @param runner The LowLevelNetRunner object
     * @param delayedResponse Set to true is you will use the
     * waitForRequestSent/waitForRequestResponse routines
     */
    public void setNetRunner(LowLevelNetRunner runner,
        boolean delayedResponse) {
      netRunner = runner;
      useLowLevel = true;
      delayResponse = delayedResponse;

      if (!delayResponse)
        netRunner.incrementRunCount();
    }

    /**
     * Enable this listeners Request object to read server responses.
     * This should only be used in conjunction with setDelayResponse(true)
     */
    public void waitForRequestResponse() {
      if (!delayResponse || !useLowLevel) {
        Log.d(LOGTAG, " Cant do this without delayReponse set ");
        return;
      }

      //if (mRequest != null) {
          // mRequest.startReadingResponse();
      // }
      /* Now wait for the response to be completed either through endData
       * or an error
       */
      synchronized (syncObj) {
        try {
          syncObj.wait();
        } catch (InterruptedException e) {
        }
      }
    }

    /**
     * Enable this listeners Request object to read server responses.
     * This should only be used in conjunction with setDelayResponse(true)
     */
    public void waitForRequestSent() {
      if (!delayResponse || !useLowLevel) {
        Log.d(LOGTAG, " Cant do this without delayReponse set ");
        return;
      }

      /* Now wait for the response to be completed either through endData
       * or an error
       */
      synchronized (syncObj) {
        try {
          syncObj.wait();
        } catch (InterruptedException e) {
        }
      }
    }

    /* Test expected values - these routines set the tests expectations */

    public void expectRequestSent() {
        expects[TEST_REQUEST_SENT] = true;
    }

    public void expectNoRequestSent() {
        notExpecting[TEST_REQUEST_SENT] = true;
    }

    public void expectStatus() {
        expects[TEST_STATUS] = true;
    }

    public void expectNoStatus() {
        notExpecting[TEST_STATUS] = true;
    }

    public void expectStatus(int major, int minor, int code) {
        expects[TEST_STATUS] = true;
        expectMajor = major;
        expectMinor = minor;
        expectCode = code;
    }

    public void expectStatus(int code) {
        expects[TEST_STATUS] = true;
        expectCode = code;
    }

    public void expectHeaders() {
        expects[TEST_HEADERS] = true;
    }

    public void expectNoHeaders() {
        notExpecting[TEST_HEADERS] = true;
    }

    public void expectHeaderAdd(String name) {
        expects[TEST_HEADERS] = true;
        TestHeader h = new TestHeader(name.toLowerCase(), null);
        expectHeaders.add(h);
    }

    public void expectHeaderAdd(String name, String value) {
        expects[TEST_HEADERS] = true;
        TestHeader h = new TestHeader(name.toLowerCase(), value);
        expectHeaders.add(h);
    }

    public void expectLocationChanged() {
        expects[TEST_LOCATION_CHANGED] = true;
    }

    public void expectNoLocationChanged() {
            notExpecting[TEST_LOCATION_CHANGED] = true;
    }

    public void expectLocationChanged(String newLocation) {
        expects[TEST_LOCATION_CHANGED] = true;
        expectLocation = newLocation;
    }

    public void expectLocationChanged(String newLocation, boolean permanent) {
        expects[TEST_LOCATION_CHANGED] = true;
        expectLocation = newLocation;
        expectPermanent = permanent ? 1 : 0;
    }

    public void expectData() {
        expects[TEST_DATA] = true;
    }

    public void expectNoData() {
        notExpecting[TEST_DATA] = true;
    }

    public void expectData(int len) {
        expects[TEST_DATA] = true;
        expectDataLength = len;
    }

    public void expectData(byte[] data, int len) {
        expects[TEST_DATA] = true;
        expectData = new byte[len];
        expectDataLength = len;

        for (int i = 0; i < len; i++) {
            expectData[i] = data[i];
        }
    }

    public void expectEndData() {
        expects[TEST_ENDDATA] = true;
    }

    public void expectNoEndData() {
            notExpecting[TEST_ENDDATA] = true;
    }

    public void expectError() {
        expects[TEST_ERROR] = true;
    }

    public void expectNoError() {
        notExpecting[TEST_ERROR] = true;
    }

    public void expectError(int errorId) {
        expects[TEST_ERROR] = true;
        expectErrorId = errorId;
    }

    public void expectSSLCertificateError() {
        expects[TEST_SSL_CERTIFICATE_ERROR] = true;
    }

    public void expectNoSSLCertificateError() {
            notExpecting[TEST_SSL_CERTIFICATE_ERROR] = true;
    }

    public void expectSSLCertificateError(int errors) {
        expects[TEST_SSL_CERTIFICATE_ERROR] = true;
        expectSslErrors = errors;
    }

    public void expectSSLCertificateError(SslCertificate certificate) {
        expects[TEST_SSL_CERTIFICATE_ERROR] = true;
        expectCertificate = certificate;
    }

    /**
     * Test to see if current expectations match recieved information
     * @return True is all expected results have been matched
     */
    public boolean expectPassed() {
        for (int i = 0; i < TEST_NUM_EXPECTS; i++) {
            if (expects[i] == true) {
                return false;
            }
        }

        for (int i = 0; i < TEST_NUM_EXPECTS; i++) {
            if (eventsReceived[i] && notExpecting[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * Return message indicating expectation failures
     */
    public String getFailureMessage() {
        return expectDetails.toString();
    }

    /**
     * Reset all expectation values for re-use
     */
    public void resetExpects() {
        expectMajor = -1;
        expectMinor = -1;
        expectCode = -1;
        expectLocation = null;
        expectPermanent = -1;
        expectErrorId = -1;
        expectSslErrors = -1;
        expectCertificate = null;
        expectDetails.setLength(0);
        expectHeaders.clear();

        for (int i = 0; i < TEST_NUM_EXPECTS; i++) {
            expects[i] = false;
            notExpecting[i] = false;
            eventsReceived[i] = false;
        }

        for (int i = 0; i < expectDataLength; i++) {
            expectData[i] = 0;
        }

        expectDataLength = -1;
    }

    /**
     * Attach the RequestHandle to this handler
     * @param requestHandle The RequestHandle
     */
    public void attachRequestHandle(RequestHandle requestHandle) {
        if (Config.LOGV) {
            Log.v(LOGTAG, "TestEventHandler.attachRequestHandle(): " +
                    "requestHandle: " +  requestHandle);
        }
        mRequestHandle = requestHandle;
    }

    /**
     * Detach the RequestHandle
     */
    public void detachRequestHandle() {
        if (Config.LOGV) {
            Log.v(LOGTAG, "TestEventHandler.detachRequestHandle(): " +
                    "requestHandle: " + mRequestHandle);
        }
        mRequestHandle = null;
    }

    protected final static String LOGTAG = "http";
}
