iracing.webapi.IracingWebApi.java Source code

Java tutorial

Introduction

Here is the source code for iracing.webapi.IracingWebApi.java

Source

package iracing.webapi;

import java.io.*;
import java.net.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.*;
import javax.security.auth.login.LoginException;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

/**
 *
 * @author Christian Aylward
 * @version 1.0.0
 */
public class IracingWebApi {

    public interface LoginRequiredHandler {
        boolean onLoginCredentialsRequired(IracingLoginCredentials creds);
    }

    public static final int VERSION = 1;

    // TODO: add event handlers for friend requests and new PM notifications
    //       so they are not missed

    // TODO
    // - Provide the ability to get world records

    private static final String MAINTENANCE_URL = "http://members.iracing.com/updating/maintenanceupdate.htm";
    private static final String MESSAGE_INBOX_URL = "http://members.iracing.com/jforum/pm/inbox.page";
    private static final String MESSAGE_READ_URL = "http://members.iracing.com/jforum/pm/read/";
    private static final String LOGIN_URL = "https://members.iracing.com/membersite/Login";
    private static final String ACCOUNT_URL = "https://members.iracing.com/membersite/account/Home.do";
    private static final String FORUM_LOGIN_URL = "http://members.iracing.com/jforum/search/filters.page"; // the search page is used as it's a small page that still requires login
    private static final String FORUM_POST_PAGE_URL = "http://members.iracing.com/jforum/jforum.page";
    private static final String DRIVER_PROFILE_URL = "http://members.iracing.com/membersite/member/Profile.do";
    private static final String GET_RESULT_SUMMARIES_URL = "http://members.iracing.com/memberstats/member/GetResults";
    private static final String GET_HOSTED_RESULT_SUMMARIES_URL = "http://members.iracing.com/memberstats/member/GetPrivateSessionResults";
    private static final String GET_HOSTED_SESSIONS_URL = "http://members.iracing.com/membersite/member/GetHostedSessions?ts=0";
    private static final String GET_RACE_RESULT_CSV_URL = "http://members.iracing.com/membersite/member/GetEventResultsAsCSV";
    private static final String GET_SESSION_DRIVER_LAPS_URL = "http://members.iracing.com/membersite/member/GetLaps";
    private static final String GET_SESSION_DRIVERS_URL = "http://members.iracing.com/membersite/member/GetSessionDrivers";
    private static final String GET_OPEN_SESSION_DRIVERS_URL = "http://members.iracing.com/membersite/member/GetOpenSessionDrivers"; // open practice sessions
    private static final String GET_HOSTED_SESSION_DRIVERS_URL = "http://members.iracing.com/membersite/member/GetPrivateSessionRegistered"; // hosted sessions
    private static final String DRIVER_SEARCH_URL = "http://members.iracing.com/membersite/member/GetDriverStatus";
    private static final String GLOBAL_DATA_URL = "http://members.iracing.com/membersite/member/HostedSessions.do"; //"http://members.iracing.com/membersite/member/CareerStats.do"; //"http://members.iracing.com/membersite/member/SeriesStandings.do?season=672";
    private static final String GET_SEASON_STANDINGS_URL = "http://members.iracing.com/memberstats/member/GetSeasonStandings";
    private static final String GET_SEASON_QUALIFYING_RESULTS_URL = "http://members.iracing.com/memberstats/member/GetSeasonQualifyResults";
    private static final String GET_SEASON_TIME_TRIAL_STANDINGS_URL = "http://members.iracing.com/memberstats/member/GetSeasonTTStandings";
    private static final String GET_TIME_TRIAL_RESULTS_URL = "http://members.iracing.com/memberstats/member/GetSeasonTTResults";
    private static final String GET_SPECTATOR_SESSIONS_URL = "http://members.iracing.com/membersite/member/GetSpectatorSessions";
    private static final String GET_WORLD_RECORDS_URL = "http://members.iracing.com/memberstats/member/GetWorldRecords";
    private static final String GET_DRIVER_STATS_URL = "http://members.iracing.com/memberstats/member/GetDriverStats";

    private static final String CERT_STORE_PASSWORD = "changeit";

    private static final String JSESSIONID = "JSESSIONID";
    private static final String JFORUMSESSIONID = "JFORUMSESSIONID";
    private static final String SET_COOKIE = "Set-Cookie";
    private static final String COOKIE = "Cookie";
    private static final String BOUNDRY = "---------------------------41184676334";
    private static final String lineEnd = "\r\n";
    private static final String twoHyphens = "--";

    private static final int CUSTOMER_DEFINITION_TYPE_ID = 1;
    private static final int CUSTOMER_DEFINITION_TYPE_NAME = 2;

    private String cookie = "";
    private Map<String, String> cookieMap;
    //    private GlobalDataDao globalDataDao;
    //    private GlobalDataParser globalData;
    private GlobalData globalData;
    private long bytesReceived = 0;
    private LoginRequiredHandler loginRequiredHandler;

    public IracingWebApi() {
        cookieMap = new HashMap<String, String>();
    }

    public void setLoginRequiredHandler(LoginRequiredHandler handler) {
        loginRequiredHandler = handler;
    }

    public long getBytesReceived() {
        return bytesReceived;
    }

    public void resetBytesReceived() {
        bytesReceived = 0;
    }

    /**
     * Test the API.
     * @param args 
     */
    public static void main(String[] args) {
        IracingWebApi webApi = new IracingWebApi();
        webApi.setLoginRequiredHandler(new LoginRequiredHandler() {
            @Override
            public boolean onLoginCredentialsRequired(IracingLoginCredentials creds) {
                creds.setEmailAddress(""); // <- Set your iRacing email address here
                creds.setPassword(""); // <- Set your iRacing password here
                return true;
            }
        });
        try {
            //            long customerId = webApi.lookupCustomerId("Christian Aylward");
            //            System.err.println("customer id : " + customerId);

            //System.err.println("apiUser: { id:" + webApi.accountInfo.getCustomer().getId() + ", name:\"" + webApi.accountInfo.getCustomer().getName() + "\" }");

            //            String name = webApi.lookupCustomerName(29462);
            //            System.err.println("customer name : " + name);

            //            String[] userData = webApi.getUserData();
            //            if (userData != null) {
            //                for (String s : userData) System.out.println(s);
            //            }

            // Send a private message by name
            //            SendPrivateMessageResult result = webApi.sendPrivateMessage("Christian Aylward", "testing again", "..");
            // Send a private message by customer ID
            //            SendPrivateMessageResult result = webApi.sendPrivateMessage(29462, "testing again", "..");
            //            System.err.println("Send message result : " + result);
            //            SendPrivateMessageResult result = SendPrivateMessageResult.SUCCESS;
            //            while (result == SendPrivateMessageResult.SUCCESS) {
            //                result = webApi.sendPrivateMessage("Christian Aylward", "testing for full mailbox", "..");
            //            }

            //            List<PrivateMessageHeader> messages = webApi.getAllPrivateMessages();
            //            if (messages != null) {
            //                System.err.println("Parsed " + messages.size() + " messages");
            //                System.err.println("------------------------------");
            //                for (PrivateMessageHeader pm : messages) {
            //                    System.err.println("pm {");
            //                    System.err.println("  id: " + pm.getId());
            //                    System.err.println("  subject: " + pm.getSubject());
            //                    System.err.println("  from {");
            //                    System.err.println("    id: " + pm.getFrom().getId());
            //                    System.err.println("    name: " + pm.getFrom().getName());
            //                    System.err.println("  }");
            //                    System.err.println("  date: " + pm.getDate().toLocaleString());
            //                    System.err.println("}");
            //                }
            //            }

            //            List<Long> messageIds = new ArrayList();
            //            messageIds.add(3326572L);
            //            messageIds.add(3301088L);
            //            webApi.deletePrivateMessages(messageIds);
            //            webApi.deletePrivateMessage(3372968L);

            //            SessionResultSummary.SearchParameters params = new SessionResultSummary.SearchParameters(true, false);
            //            params.setIncludeOfficialSessions(true);
            //            params.setIncludeUnofficialSessions(true);
            //            params.setIncludeQualifyingSessions(true);
            //            params.setCustomerId(27251);
            //            params.setSeasonQuarter(2);
            //            params.setUpperBound(10);
            //            List<SessionResultSummary> list = webApi.getSessionResultSummaries(params);
            //            for (SessionResultSummary summary : list) {
            //                System.err.println(summary.getSubSessionId());
            //            }

            //            List<HostedSessionSummary> hostedSessions = webApi.getHostedSessions();
            //            for (HostedSessionSummary summary : hostedSessions) {
            //                System.err.println("------------------");
            //                System.err.println(summary.getSessionName());
            //                if (!"".equals(summary.getSessionDescription())) {
            //                    System.err.println(summary.getSessionDescription());
            //                }
            //            }

            //            HostedSessionResultSummary.SearchParameters params = new HostedSessionResultSummary.SearchParameters();
            //            params.setParticipantCustomerId(29462);
            //            params.setEnd(20);
            //            HostedSessionResultSummaries summaries = webApi.getHostedSessionResultSummaries(params);
            //            for (HostedSessionResultSummary summary : summaries.getResults()) {
            //                System.err.println(summary.getSessionName());
            //            }

            //            long subSessionId = 5916582L;
            //            long customerId = 34038;
            //            SessionDriverLaps laps1 = webApi.getSessionDriverLaps(customerId, subSessionId, 0);
            //            SessionDriverLaps laps2 = webApi.getSessionDriverLaps(customerId, subSessionId, -1);
            //            SessionDriverLaps laps3 = webApi.getSessionDriverLaps(customerId, subSessionId, -2);
            //            System.err.println("== Race laps ==");
            //            for (SessionDriverLap lap : laps1.getLaps()) {
            //                double time = (double)lap.getLapTime() / 10000;
            //                System.err.println(lap.getLapNumber() + " : " + time);
            //            }
            //            System.err.println("== Qualifying laps ==");
            //            for (SessionDriverLap lap : laps2.getLaps()) {
            //                double time = (double)lap.getLapTime() / 10000;
            //                System.err.println(lap.getLapNumber() + " : " + time);
            //            }
            //            System.err.println("== Practice laps ==");
            //            for (SessionDriverLap lap : laps3.getLaps()) {
            //                double time = (double)lap.getLapTime() / 10000;
            //                System.err.println(lap.getLapNumber() + " : " + time);
            //            }

            //            SessionResult sessionResult = webApi.getSessionResult(5916582, true); //5923645); //5861385); //5878460);
            //            if (sessionResult != null) {
            //                System.err.println("=================");
            //                System.err.println(sessionResult.getSeriesName());
            //                System.err.println("-----------------");
            //                for (SessionSubResult result : sessionResult.getSubResults()) {
            //                    System.err.println(getEventTypeName(result.getEventTypeId()).toUpperCase());
            //                    System.err.println("-----------------");
            //                    for (SessionDriverResult driverResult : result.getDriverResults()) {
            //                        System.err.println(driverResult.getCustomerName() + " : " + driverResult.getFastestLapTime() + " (" + driverResult.getDriverLaps().getLaps().size() + ")");
            //                    }
            //                    System.err.println("-----------------");
            //                }
            //            }

            //            SessionDriverList drivers = webApi.getLiveSessionDrivers(5878460);
            //            for (SessionDriver driver : drivers.getDrivers()) {
            //                System.err.println(driver.getCustomerId() + " : " + driver.getName());
            //            }

            //            List<HostedSessionSummary> hostedSessions = webApi.getLiveHostedSessions();
            //            for (HostedSessionSummary hostedSession : hostedSessions) {
            //                String host = hostedSession.getHost().getName();
            //                System.err.println("==========================");
            //                System.err.println(hostedSession.getSessionName() + " (" + host + ")");
            //                System.err.println("--------------------------");
            //                SessionDriverList drivers = webApi.getLiveHostedSessionDrivers(hostedSession.getSubSessionId(), hostedSession.getPrivateSessionId());
            //                for (SessionDriver driver : drivers.getDrivers()) {
            //                    System.err.println(driver.getName());
            //                }
            //                System.err.println("==========================");
            //            }

            SeasonStandings.SearchParameters params = new SeasonStandings.SearchParameters();
            params.setEnd(5);
            params.setSeasonId(672);
            long totalRecords = webApi.getSeasonStandings(params, new SeasonStandingsParser.ItemHandler() {
                @Override
                public boolean onSeasonStandingParsed(SeasonStanding standing) {
                    System.err.println(standing.getDriverName() + " (" + standing.getTotalPoints() + ")");
                    return true;
                }
            });
            System.err.println("TOTAL RECORDS : " + totalRecords);

            //            SeasonQualifyingResults.SearchParameters params = new SeasonQualifyingResults.SearchParameters();
            //            params.setEnd(5);
            //            params.setCarClassId(25);
            //            params.setSeasonId(672);
            //            SeasonQualifyingResults results = webApi.getSeasonQualifyingResults(params);
            //            for (SeasonQualifyingResult result : results.getResults()) {
            //                System.err.println(result.getDriverName() + " (" + result.getBestQualifyingTimeFormatted() + ")");
            //            }
            //            System.err.println("TOTAL RECORDS : " + results.getTotalRecords());

            //            SeasonTimeTrialStandings.SearchParameters params = new SeasonTimeTrialStandings.SearchParameters();
            //            params.setEnd(5);
            //            params.setCarClassId(25);
            //            params.setSeasonId(672);
            //            SeasonTimeTrialStandings standings = webApi.getSeasonTimeTrialStandings(params);
            //            for (SeasonTimeTrialStanding standing : standings.getStandings()) {
            //                System.err.println(standing.getDriverName() + " (" + standing.getTotalPoints() + ")");
            //            }
            //            System.err.println("TOTAL RECORDS : " + standings.getTotalRecords());

            //            TimeTrialResultStandings.SearchParameters params = new TimeTrialResultStandings.SearchParameters();
            //            params.setCarClassId(25);
            //            params.setSeasonId(672);
            //            params.setRaceWeek(8);
            //            params.setEnd(5);
            //            //params.setDivision(DIVISION_ALL);
            //            TimeTrialResultStandings standings = webApi.getTimeTrialResultStandings(params);
            //            for (TimeTrialResultStanding standing : standings.getStandings()) {
            //                System.err.println(standing.getDriverName() + " (" + standing.getPoints() + ")");
            //            }
            //            System.err.println("TOTAL RECORDS : " + standings.getTotalRecords());

            //            GlobalData data = webApi.getGlobalData();
            //            for (Division d : data.getDivisions()) {
            //                System.err.println(d.getShortName());
            //            }
            //            for (EventType e : data.getEventTypes()) {
            //                System.err.println(e.getName());
            //            }
            //            for (Category c : data.getCategories()) {
            //                System.err.println(c.getName());
            //            }
            //            for (License l : data.getLicenses()) {
            //                System.err.println(l.getFullName());
            //            }
            //            for (Track t : data.getTracks()) {
            //                String s = t.getName();
            //                if (!"".equals(t.getConfigName())) s += " - " + t.getConfigName();
            //                System.err.println(s);
            //            }
            //            for (Car c : data.getCars()) {
            //                System.err.println(c.getName());
            //            }
            //            for (CarClass cc : data.getCarClasses()) {
            //                System.err.println(cc.getName());
            //            }
            //            for (Season s : data.getSeasons()) {
            //                System.err.println(s.getSeriesShortName() + " - " + s.getSeasonShortName());
            //            }
            //            for (YearAndQuarters yaq : data.getYearAndQuarters()) {
            //                System.err.println(yaq.getYear() + " : " + yaq.getQuarters().size());
            //            }
            //            for (LicenseGroup lg : data.getLicenseGroups()) {
            //                System.err.println(lg.getName());
            //            }
            //            for (LicenseLevel ll : data.getLicenseLevels()) {
            //                System.err.println(ll.getName());
            //            }
            //            for (HardcoreOption ho : data.getHardcoreOptions()) {
            //                System.err.println(ho.getName());
            //            }
            //            for (Club c : data.getClubs()) {
            //                System.err.println(c.getShortName());
            //            }

            //            getV8QualifyingStats(webApi);

            //            GlobalDataDao globalData = webApi.getGlobalData();
            ////            CarClass cc = globalData.getCarClass(25);
            ////            System.err.println(cc.getName());
            //            
            //            List<SpectatorSession> sessions = webApi.getSpectatorSessions();
            //            for (SpectatorSession session : sessions) {
            //                if (session.getBroadcastInfo().getNumberOfDrivers() > 0) {
            //                    String s = String.valueOf(session.getSubSessionId());
            //                    if (session.getHostedInfo() != null) {
            //                        s += " : " + session.getHostedInfo().getSessionName();
            //                    } else {
            //                        Season season = globalData.getSeason(session.getSeasonId());
            //                        EventType et = globalData.getEventType(session.getEventTypeId());
            //                        s += " - " + season.getSeriesName() + " (" + et.getName() + ")";
            //                    }
            //                    s += " (" + session.getBroadcastInfo().getNumberOfDrivers() + ")";
            //                    System.err.println(s);
            //                }
            //            }

            //            WorldRecord.SearchParameters params = new WorldRecord.SearchParameters();
            //            params.setCustomerId(29462);
            //            params.setCarId(33);
            //            params.setEnd(5);
            //            params.setSeasonYear(2011);
            //            params.setTrackId(46);
            //            WorldRecords records = webApi.getWorldRecords(params, true);
            //            for (WorldRecord record : records.getRecords()) {
            //                String s = record.getRecordNumber() + " : " + record.getDriver().getName();
            //                s += " (" + record.getPractice() + ")";
            //                System.err.println(s);
            //            }

            //            DriverStat.SearchParameters params = new DriverStat.SearchParameters();
            //            params.setCategoryId(2); // road
            //            params.setActiveDriversOnly(true);
            //            params.setUserFilter(UserFilter.FRIENDS);
            //            params.setEnd(10);
            //            DriverStats stats = webApi.getDriverStats(params, false);
            //            for (DriverStat stat : stats.getStats()) {
            //                System.err.println(stat.getRecordNumber() + " : " + stat.getIrating() + " " + stat.getDriver().getName());
            //            }

            System.err.println("Bytes received : " + webApi.getBytesReceived());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private static void getV8QualifyingStats(IracingWebApi webApi) throws IOException, LoginException {
        // get a list of all drivers who have set a qualifying time
        SeasonQualifyingResults.SearchParameters params = new SeasonQualifyingResults.SearchParameters();
        params.setCarClassId(25);
        params.setSeasonId(672);
        params.setRaceWeek(8);
        params.setStart(1);
        params.setEnd(25);
        SeasonQualifyingResults results = webApi.getSeasonQualifyingResults(params);
        // for each driver who has set a time, check how many qualifying sessions they've entered
        for (SeasonQualifyingResult result : results.getResults()) {
            SessionResultSummary.SearchParameters params2 = new SessionResultSummary.SearchParameters(true, false);
            params2.setIncludeOfficialSessions(true);
            params2.setIncludeUnofficialSessions(true);
            params2.setIncludeQualifyingSessions(true);
            params2.setCustomerId(result.getDriverCustomerId());
            params2.setSeriesId(88);
            params2.setCarClassId(25);
            params2.setCarId(28);
            params2.setSeasonYear(2012);
            params2.setSeasonQuarter(2);
            params2.setRaceWeek(8);
            List<SessionResultSummary> summaries = webApi.getSessionResultSummaries(params2);
            //            for (SessionResultSummary summary : summaries) {
            //                System.err.println("  " + summary.getDisplayName());
            //            }
            System.err
                    .println(result.getPosition() + " : " + result.getDriverName() + " (" + summaries.size() + ")");
        }
    }

    private void createCookieFromMap() {
        StringBuilder sb = new StringBuilder();
        for (String key : cookieMap.keySet()) {
            if (sb.length() > 0)
                sb.append("; ");
            sb.append(key).append("=").append(cookieMap.get(key));
        }
        cookie = sb.toString();
        //        System.err.println("Cookie is : " + cookie);
    }

    private void installCerts() throws Exception {
        String host = "members.iracing.com";
        int port = 443;

        char[] password = CERT_STORE_PASSWORD.toCharArray();

        File file = new File("jssecacerts");
        if (!file.isFile()) {
            char seperator = File.separatorChar;
            File dir = new File(System.getProperty("java.home") + seperator + "lib" + seperator + "security");
            file = new File(dir, "jssecacerts");
            if (!file.isFile()) {
                file = new File(dir, "cacerts");
            }
        }
        KeyStore ks;
        InputStream in = new FileInputStream(file);
        ks = KeyStore.getInstance(KeyStore.getDefaultType());
        try {
            ks.load(in, password);
        } catch (Exception e) {
        }
        in.close();

        SSLContext context = SSLContext.getInstance("TLS");
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(ks);
        X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
        SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
        context.init(null, new TrustManager[] { tm }, null);
        SSLSocketFactory factory = context.getSocketFactory();

        SSLSocket socket = null;
        try {
            socket = (SSLSocket) factory.createSocket(host, port);
            socket.setSoTimeout(10000);
            socket.startHandshake();
        } catch (Exception e) {
            //e.printStackTrace();
        } finally {
            if (socket != null)
                socket.close();
        }

        X509Certificate[] chain = tm.chain;
        if (chain == null)
            return;

        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");
        for (int i = 0; i < chain.length; i++) {
            X509Certificate cert = chain[i];
            sha1.update(cert.getEncoded());
            md5.update(cert.getEncoded());
        }

        for (int count = 0; count < chain.length; count++) {
            X509Certificate cert = chain[count];
            String alias = host + "-" + (count + 1);
            ks.setCertificateEntry(alias, cert);
            OutputStream out = new FileOutputStream("jssecacerts");
            try {
                ks.store(out, password);
            } finally {
                out.close();
            }
        }
    }

    private boolean isMaintenancePage(URLConnection conn) {
        String location = conn.getHeaderField("Location");
        if (location != null && location.startsWith(MAINTENANCE_URL)) {
            System.err.println("iRacing website is down for maintenance");
            return true;
        }
        return false;
    }

    /**
     * 
     * @return a login response code
     * @throws IOException
     * @throws LoginException 
     * @see #LOGIN_RESPONSE_SUCCESS
     * @see #LOGIN_RESPONSE_CONNECTION_ERROR
     * @see #LOGIN_RESPONSE_DOWN_FOR_MAINTAINENCE
     * @see #LOGIN_RESPONSE_FAILED_CREDENTIALS
     */
    public LoginResponse login() throws IOException, LoginException {
        try {
            installCerts();
        } catch (Exception e1) {
            e1.printStackTrace();
            throw new LoginException("error whilst attempting to install SSL certificates");
        }

        System.setProperty("javax.net.ssl.trustStore", "jssecacerts");
        System.setProperty("javax.net.ssl.trustStorePassword", CERT_STORE_PASSWORD);

        if (loginRequiredHandler == null)
            return LoginResponse.ConfigError;
        IracingLoginCredentials creds = new IracingLoginCredentials();
        if (!loginRequiredHandler.onLoginCredentialsRequired(creds))
            return LoginResponse.CredentialsError;
        String encodedUsername = URLEncoder.encode(creds.getEmailAddress(), "UTF-8");
        String encodedPW = URLEncoder.encode(creds.getPassword(), "UTF-8");

        String urltext = LOGIN_URL + "?username=" + encodedUsername + "&password=" + encodedPW; // + "&utcoffset=-60&todaysdate=";

        URL url = new URL(urltext);

        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        conn.addRequestProperty("Content-Length", "0");

        conn.setInstanceFollowRedirects(false);
        HttpsURLConnection.setFollowRedirects(false);

        try {
            conn.connect();
        } catch (Exception e) {
            e.printStackTrace();
            throw new LoginException(e.getMessage());
        }

        if (isMaintenancePage(conn))
            return LoginResponse.DownForMaintenance;

        String headerName;
        for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
            if (headerName.equalsIgnoreCase(SET_COOKIE)) {
                addToCookieMap(conn.getHeaderField(i));
            } else {
                if (!headerName.equals("Location")) {
                    continue;
                }
                String location2 = conn.getHeaderField(i);

                if (location2.indexOf("failedlogin") != -1) {
                    throw new LoginException("You have been directed to the failed login page");
                }
            }
        }
        createCookieFromMap();

        conn.disconnect();

        return LoginResponse.Success;
    }

    private void addToCookieMap(String setCookieHeader) {
        StringTokenizer st = new StringTokenizer(setCookieHeader, ";");
        if (st.hasMoreTokens()) {
            String token = st.nextToken();
            String name = token.substring(0, token.indexOf('='));
            String value = token.substring(token.indexOf('=') + 1, token.length());
            cookieMap.put(name, value);
        }
    }

    private boolean forumLoginAndGetCookie() {
        try {
            // Make a connect to the server
            URL url = new URL(FORUM_LOGIN_URL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            conn.addRequestProperty(COOKIE, cookie);

            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setInstanceFollowRedirects(false);
            HttpURLConnection.setFollowRedirects(false);

            conn.connect();

            if (isMaintenancePage(conn))
                return false;

            String headerName;
            boolean containsCookie = false;
            for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
                if (headerName.equalsIgnoreCase(SET_COOKIE)) {
                    containsCookie = true;
                    addToCookieMap(conn.getHeaderField(i));
                }
            }
            if (containsCookie)
                createCookieFromMap();

            conn.disconnect();
        } catch (Exception ex) {
            ex.printStackTrace();
            return false;
        }
        return true;
    }

    private String getResponseText(URLConnection conn) throws IOException {
        byte[] bytes;
        // NOTE: The response is read into memory first before unzipping
        //       because it resulted in a 200% performance increase
        //       as opposed to unzipping directly from the connection inputstream
        // Read the response in to memory (GZipped or not)
        InputStream is = conn.getInputStream();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            byte[] b = new byte[1024];
            int bytesRead;
            while ((bytesRead = is.read(b)) != -1) {
                baos.write(b, 0, bytesRead);
            }
            bytes = baos.toByteArray();
        } finally {
            baos.close();
            is.close();
        }
        //        System.err.println(conn.getURL().toString());
        this.bytesReceived += bytes.length;
        // Unzip the response if necessary
        if ("gzip".equals(conn.getContentEncoding())) {
            //            System.err.println("compressed : " + bytesReceived.length + " bytes");
            GZIPInputStream is2 = null;
            try {
                is2 = new GZIPInputStream(new ByteArrayInputStream(bytes));
                baos = new ByteArrayOutputStream();
                byte[] b = new byte[1024];
                int bytesRead;
                while ((bytesRead = is2.read(b)) != -1) {
                    baos.write(b, 0, bytesRead);
                }
                bytes = baos.toByteArray();
            } finally {
                baos.close();
                is2.close();
            }
        }
        //        System.err.println("uncompressed : " + bytesReceived.length + " bytes");
        return new String(bytes);
    }

    private void addMultipartFormData(StringBuilder data, String key, String value) {
        data.append(twoHyphens).append(BOUNDRY).append(lineEnd);
        data.append("Content-Disposition: form-data; name=\"").append(key).append('"').append(lineEnd)
                .append(lineEnd);
        if (value != null)
            data.append(value);
        data.append(lineEnd);
    }

    private String mapToRequestParamaters(Map<String, String> parameters) throws UnsupportedEncodingException {
        StringBuilder content = new StringBuilder();
        for (String key : parameters.keySet()) {
            if (content.length() > 0)
                content.append("&");
            content.append(key).append("=");
            content.append(URLEncoder.encode(parameters.get(key), "UTF-8"));
        }
        return content.toString();
    }

    private String doPostRequestUrlEncoded(boolean needForumCookie, String url, Map<String, String> parameters)
            throws IOException, LoginException {
        return doPostRequestUrlEncoded(needForumCookie, url, mapToRequestParamaters(parameters));
    }

    private String doPostRequestUrlEncoded(boolean needForumCookie, String url, String parameters)
            throws IOException, LoginException {
        if (!cookieMap.containsKey(JSESSIONID)) {
            if (login() != LoginResponse.Success)
                return null;
        }
        if (needForumCookie && !cookieMap.containsKey(JFORUMSESSIONID)) {
            if (!forumLoginAndGetCookie())
                return null;
        }
        String output = null;
        try {
            // URL of CGI-Bin script.
            URL oUrl = new URL(url);
            // URL connection channel.
            HttpURLConnection urlConn = (HttpURLConnection) oUrl.openConnection();
            // Let the run-time system (RTS) know that we want input.
            urlConn.setDoInput(true);
            // Let the RTS know that we want to do output.
            urlConn.setDoOutput(true);
            // No caching, we want the real thing.
            urlConn.setUseCaches(false);
            urlConn.addRequestProperty(COOKIE, cookie);

            // request to have the response gzipped (bringing a 3.5Mb response down to 175kb)
            urlConn.addRequestProperty("Accept-Encoding", "gzip");

            urlConn.setRequestMethod("POST");
            // Specify the content type.
            urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
            DataOutputStream printout = new DataOutputStream(urlConn.getOutputStream());
            try {
                printout.writeBytes(parameters);
                printout.flush();
            } finally {
                printout.close();
            }

            output = getResponseText(urlConn);
            //            System.out.println(output);

            urlConn.disconnect();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    private String doGetRequest(boolean needForumCookie, String url) throws IOException, LoginException {
        return doGetRequest(needForumCookie, url, null);
    }

    private String doGetRequest(boolean needForumCookie, String url, String userAgent)
            throws IOException, LoginException {
        if (!cookieMap.containsKey(JSESSIONID)) {
            if (login() != LoginResponse.Success)
                return null;
        }
        if (needForumCookie && !cookieMap.containsKey(JFORUMSESSIONID)) {
            if (!forumLoginAndGetCookie())
                return null;
        }
        String output = null;
        try {
            // Make a connection
            URL oUrl = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) oUrl.openConnection();

            conn.addRequestProperty(COOKIE, cookie);

            // request to have the response gzipped (bringing a 3.5Mb response down to 175kb)
            conn.addRequestProperty("Accept-Encoding", "gzip");
            if (userAgent != null)
                conn.addRequestProperty("User-Agent", userAgent);

            //            conn.setDoInput(true);
            //            conn.setUseCaches(false);
            //            conn.setInstanceFollowRedirects(false);
            //            HttpURLConnection.setFollowRedirects(false);

            //        logger.debug("opening connection to " + url.getFile());
            conn.connect();

            if (isMaintenancePage(conn))
                return null;

            // Ensure we got the HTTP 200 response code
            int responseCode = conn.getResponseCode();
            if (responseCode != 200) {
                throw new Exception(String.format("Received the response code %d from the URL %s : %s",
                        responseCode, url, conn.getResponseMessage()));
            }

            // Read the response
            output = getResponseText(conn);
            //            System.out.println(output);

            conn.disconnect();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    //    public void setGlobalDataDao(GlobalDataDao dao) {
    //        this.globalDataDao = dao;
    //    }

    //    private void updateGlobalData() throws IOException, LoginException {
    //        String html = doGetRequest(false, "http://members.iracing.com/membersite/member/SeriesStandings.do?season=672");
    //        globalData = new GlobalDataParser();
    //        globalData.parse(html);
    //    }

    //    public GlobalDataParser getGlobalData() throws IOException, LoginException {
    //        if (globalData == null) {
    //            updateGlobalData();
    //        }
    //        return globalData;
    //    }

    //    public GlobalDataDao getGlobalData() throws IOException, LoginException {
    //        if (globalDataDao == null) {
    //            globalDataDao = new GlobalDataInMemoryDao();
    //        }
    //        if (globalDataDao.getLastUpdate() == null) {
    //            String html = doGetRequest(false, GLOBAL_DATA_URL);
    //            GlobalDataParser.parse(html, globalDataDao);
    //        }
    //        return globalDataDao;
    //    }

    public GlobalData getGlobalData() throws IOException, LoginException {
        if (globalData == null) {
            String html = doGetRequest(false, GLOBAL_DATA_URL);
            globalData = GlobalDataParser.parse(html);
        }
        return globalData;
    }

    private String lookupCustomer_GetResponse(String customerNameOrId) throws IOException, LoginException {
        return doGetRequest(false,
                DRIVER_SEARCH_URL + "?searchTerms=" + URLEncoder.encode(customerNameOrId, "UTF-8"));
    }

    public String lookupCustomerName(long customerId) throws IOException, LoginException {
        // Valid ID's start at 15052
        if (customerId < 15052)
            return null;
        // example output
        //{"friends":false,"search":1,"studied":false,"searchRacers":[{"userRole":0,"lastSeen":0,"broadcast":{},"privateSession":{},"spotterAccess":0,"lastLogin":1337689089000,"subSessionStatus":"","name":"Christian+Aylward","helmet":{"ll":14,"c3":95,"hp":19,"c1":253,"c2":71},"regOpen":false,"custid":29462}],"fsRacers":[]}
        String output = null, response = lookupCustomer_GetResponse(String.valueOf(customerId));
        if (response == null)
            return null;
        try {
            JSONParser parser = new JSONParser();
            JSONObject jsonObj = (JSONObject) parser.parse(response);
            JSONArray jsonSearchResults = (JSONArray) jsonObj.get("searchRacers");
            for (int i = 0; i < jsonSearchResults.size(); i++) {
                JSONObject jsonSearchResult = (JSONObject) jsonSearchResults.get(i);
                long id = (Long) jsonSearchResult.get("custid");
                if (id == customerId) {
                    output = URLDecoder.decode((String) jsonSearchResult.get("name"), "UTF-8");
                    break;
                }
            }

        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    public long lookupCustomerId(String customerName) throws IOException, LoginException {
        // example output
        //{"friends":false,"search":1,"studied":false,"searchRacers":[{"userRole":0,"lastSeen":0,"broadcast":{},"privateSession":{},"spotterAccess":0,"lastLogin":1337689089000,"subSessionStatus":"","name":"Christian+Aylward","helmet":{"ll":14,"c3":95,"hp":19,"c1":253,"c2":71},"regOpen":false,"custid":29462}],"fsRacers":[]}
        String response = lookupCustomer_GetResponse(customerName);
        if (response == null)
            return 0;
        long output = 0;
        try {
            JSONParser parser = new JSONParser();
            JSONObject jsonObj = (JSONObject) parser.parse(response);
            JSONArray jsonSearchResults = (JSONArray) jsonObj.get("searchRacers");
            for (int i = 0; i < jsonSearchResults.size(); i++) {
                JSONObject jsonSearchResult = (JSONObject) jsonSearchResults.get(i);
                String name = URLDecoder.decode((String) jsonSearchResult.get("name"), "UTF-8");
                if (name.equals(customerName)) {
                    output = (Long) jsonSearchResult.get("custid");
                    break;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    public SendPrivateMessageResult sendPrivateMessage(long customerId, String subject, String message)
            throws IOException, LoginException {
        return sendPrivateMessage(CUSTOMER_DEFINITION_TYPE_ID, String.valueOf(customerId), subject, message);
    }

    public SendPrivateMessageResult sendPrivateMessage(String customerName, String subject, String message)
            throws IOException, LoginException {
        return sendPrivateMessage(CUSTOMER_DEFINITION_TYPE_NAME, customerName, subject, message);
    }

    private SendPrivateMessageResult sendPrivateMessage(int customerDefinitionType, String customer, String subject,
            String message) throws IOException, LoginException {
        if (!cookieMap.containsKey(JSESSIONID)) {
            if (login() != LoginResponse.Success)
                return SendPrivateMessageResult.UNABLE_TO_LOGIN;
        }
        SendPrivateMessageResult output = SendPrivateMessageResult.UNKNOWN_ERROR;
        if (!cookieMap.containsKey(JFORUMSESSIONID)) {
            if (!forumLoginAndGetCookie())
                return SendPrivateMessageResult.UNABLE_TO_LOGIN;
        }
        try {
            // Make a connection
            URL url = new URL(FORUM_POST_PAGE_URL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            //multipart/form-data
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            //conn.setInstanceFollowRedirects(true);
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDRY);

            conn.addRequestProperty(COOKIE, cookie);

            StringBuilder data = new StringBuilder();

            // set the multipart form data parameters
            addMultipartFormData(data, "action", "sendSave");
            addMultipartFormData(data, "module", "pm");
            addMultipartFormData(data, "preview", "0");
            addMultipartFormData(data, "start", null);
            if (customerDefinitionType == CUSTOMER_DEFINITION_TYPE_ID) {
                addMultipartFormData(data, "toUsername", null);
                addMultipartFormData(data, "toUserId", customer);
            } else if (customerDefinitionType == CUSTOMER_DEFINITION_TYPE_NAME) {
                addMultipartFormData(data, "toUsername", customer);
                addMultipartFormData(data, "toUserId", null);
            }
            addMultipartFormData(data, "disa1ble_html", "on");
            addMultipartFormData(data, "attach_sig", "on");
            addMultipartFormData(data, "subject", subject);
            addMultipartFormData(data, "message", message);
            addMultipartFormData(data, "addbbcode24", "#444444");
            addMultipartFormData(data, "addbbcode26", "12");
            addMultipartFormData(data, "helpbox", "Italic Text: [i]Text[/i]  (alt+i)");

            data.append(twoHyphens).append(BOUNDRY).append(twoHyphens);
            DataOutputStream dataOS = new DataOutputStream(conn.getOutputStream());
            try {
                dataOS.writeBytes(data.toString());
                dataOS.flush();
            } finally {
                dataOS.close();
            }

            conn.connect();

            if (isMaintenancePage(conn))
                return SendPrivateMessageResult.UNABLE_TO_LOGIN;

            // Ensure we got the HTTP 200 response code
            int responseCode = conn.getResponseCode();
            if (responseCode != 200) {
                throw new Exception(String.format("Received the response code %d from the URL %s : %s",
                        responseCode, url, conn.getResponseMessage()));
            }

            String response = getResponseText(conn);
            //            System.out.println(response);

            if (response.contains("Your message was successfully sent.")) {
                output = SendPrivateMessageResult.SUCCESS;
            } else if (response.contains(
                    "Could not determine the user id. Please check if you typed the username correctly and try again.")) {
                output = SendPrivateMessageResult.USER_NOT_FOUND;
            } else if (response.contains(
                    "Sorry, but this users inbox is currently full and cannot receive private messages at this time.")) {
                output = SendPrivateMessageResult.MAILBOX_FULL;
            }

            conn.disconnect();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return output;
    }

    public void deletePrivateMessage(long messageId) throws IOException, LoginException {
        deletePrivateMessagesImpl("id=" + messageId);
    }

    public void deletePrivateMessages(List<Long> messageIds) throws IOException, LoginException {
        StringBuilder params = new StringBuilder();
        for (Long id : messageIds) {
            if (params.length() > 0)
                params.append("&");
            params.append("id=").append(id);
        }
        deletePrivateMessagesImpl(params.toString());
    }

    private void deletePrivateMessagesImpl(String ids) throws IOException, LoginException {
        StringBuilder params = new StringBuilder();
        params.append("module=").append(URLEncoder.encode("pm", "UTF-8"));
        params.append("&action=").append(URLEncoder.encode("delete", "UTF-8"));
        params.append("&").append(ids);
        params.append("&delete=").append(URLEncoder.encode(" Delete Selected ", "UTF-8"));
        String response = doPostRequestUrlEncoded(true, FORUM_POST_PAGE_URL, params.toString());
        // TODO: search for "Message deletion done." message in response
    }

    private IracingCustomer apiUser;

    private static final String USER_AGENT_FIREFOX_14 = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1";

    private IracingCustomer getApiUser() throws IOException, LoginException {
        if (apiUser == null) {
            AccountInfo ai = getAccountInfo();
            apiUser = ai.getCustomer();
        }
        return apiUser;
    }

    public List<PrivateMessageHeader> getAllPrivateMessages() throws IOException, LoginException {
        // NOTE: passing USER_AGENT_FIREFOX causes the HTML to be compressed but also adds 20k extra download for some reason
        String html = doGetRequest(true, MESSAGE_INBOX_URL, null);
        return PrivateMessageListParser.parse(true, getApiUser(), html); // TODO: handle outbox messages
    }

    public PrivateMessage getPrivateMessage(long messageId) throws IOException, LoginException {
        String html = doGetRequest(true, MESSAGE_READ_URL + messageId + ".page");
        return PrivateMessageDetailParser.parse(html);
    }

    //    private boolean getStatSessionCookie() {
    //        boolean containsStatSessionId = false;
    //        try {
    //            URL url = new URL("http://members.iracing.com/membersite/member/CareerStats.do");
    //            //URL url = new URL("http://members.iracing.com/membersite/member/results.jsp?custid=29462");
    //            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
    //            conn.setDoInput(true);
    //            conn.setUseCaches(false);
    //            conn.setInstanceFollowRedirects(false);
    //            HttpURLConnection.setFollowRedirects(false);
    //
    //            conn.addRequestProperty(COOKIE, cookie);
    //
    //            conn.connect();
    //
    //            if (isMaintenancePage(conn)) return false;
    //
    //            String headerName;
    //            for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) {
    //                if (headerName.equalsIgnoreCase("Set-Cookie")) {
    //                    StringTokenizer st = new StringTokenizer(conn.getHeaderField(i), ";");
    //                    if (st.hasMoreTokens()) {
    //                        String token = st.nextToken();
    //                        String name = token.substring(0, token.indexOf('='));
    //                        String value = token.substring(token.indexOf('=') + 1, token.length());
    //                        cookieMap.put(name, value);
    //                        if (name.equals("STATSSESSIONID")) containsStatSessionId = true;
    //                    }
    //                }
    //            }
    //            createCookieFromMap();
    //            
    //            conn.disconnect();
    //        } catch (Exception ex) {
    //            Logger.getLogger(IracingWebApi.class.getName()).log(Level.SEVERE, null, ex);
    //            return false;
    //        }
    //        return containsStatSessionId;
    //    }

    public List<SessionResultSummary> getSessionResultSummaries(SessionResultSummary.SearchParameters params)
            throws IOException, LoginException {
        SessionResultSummaryParser.ListItemHandler listHandler = new SessionResultSummaryParser.ListItemHandler();
        getSessionResultSummaries(params, listHandler);
        return listHandler.getList();
    }

    public void getSessionResultSummaries(SessionResultSummary.SearchParameters params,
            SessionResultSummaryParser.ItemHandler handler) throws IOException, LoginException {
        //        System.err.println("getSessionResultSummaries() : " + mapToRequestParamaters(params.toParameters()));
        String json = doPostRequestUrlEncoded(false, GET_RESULT_SUMMARIES_URL, params.toParameters());
        //        System.out.println(json);
        SessionResultSummaryParser.parse(json, handler);
    }

    public HostedSessionResultSummaries getHostedSessionResultSummaries(
            HostedSessionResultSummary.SearchParameters params) throws IOException, LoginException {
        HostedSessionResultSummaryParser.ListItemHandler listHandler = new HostedSessionResultSummaryParser.ListItemHandler();
        HostedSessionResultSummaries output = new HostedSessionResultSummaries();
        output.setTotalRecords(getHostedSessionResultSummaries(params, listHandler));
        output.setResults(listHandler.getList());
        return output;
    }

    public long getHostedSessionResultSummaries(HostedSessionResultSummary.SearchParameters params,
            HostedSessionResultSummaryParser.ItemHandler handler) throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_HOSTED_RESULT_SUMMARIES_URL, params.toParameters());
        return HostedSessionResultSummaryParser.parse(json, handler);
    }

    public List<HostedSessionSummary> getLiveHostedSessions() throws IOException, LoginException {
        String json = doGetRequest(false, GET_HOSTED_SESSIONS_URL);
        return HostedSessionSummaryParser.parse(json);
    }

    public static final int EVENT_TYPE_TEST = 1;
    public static final int EVENT_TYPE_PRACTICE = 2;
    public static final int EVENT_TYPE_QUALIFYING = 3;
    public static final int EVENT_TYPE_TIME_TRIAL = 4;
    public static final int EVENT_TYPE_RACE = 5;

    private static String getEventTypeName(int id) {
        switch (id) {
        case EVENT_TYPE_TEST:
            return "Test";
        case EVENT_TYPE_PRACTICE:
            return "Practice";
        case EVENT_TYPE_QUALIFYING:
            return "Qualify";
        case EVENT_TYPE_RACE:
            return "Race";
        default:
            return "Time Trial";
        }
    }

    public SessionResult getSessionResult(long subSessionId, boolean includeDriverLaps)
            throws IOException, LoginException {
        List<SessionSubResult> subResults = new ArrayList<SessionSubResult>();
        int simSessionNumber = 0;
        CsvSessionResult result1 = getSessionResult(subSessionId, simSessionNumber, includeDriverLaps, true);
        int[] iaEventTypes = null;
        // Use SessionResultSummary search to determine what sub sessions exist (i.e. practice, qualifying & race)
        if (result1.isHosted()) {
            HostedSessionResultSummary.SearchParameters params = new HostedSessionResultSummary.SearchParameters();
            params.setParticipantCustomerId(result1.getDriverResults().get(0).getCustomerId());
            params.setStartTime(new Date(result1.getStartTime().getTime() - 1000));
            params.setEndTime(new Date(result1.getStartTime().getTime() + (1000 * 60 * 60 * 4)));
            HostedSessionResultSummaries summaries = getHostedSessionResultSummaries(params);
            //            System.err.println("getSessionResult() : found " + summaries.getResults().size() + " result summaries");
            boolean containsPractice = false, containsQualifying = false, containsRace = false;
            for (HostedSessionResultSummary summary : summaries.getResults()) {
                if (summary.getSubSessionId() == subSessionId) {
                    if (summary.getPracticeLength() > 0)
                        containsPractice = true;
                    if (summary.getQualifyingLength() > 0)
                        containsQualifying = true;
                    if (summary.getRaceLength() > 0)
                        containsRace = true;
                    if (containsPractice && !containsQualifying && !containsRace) {
                        iaEventTypes = new int[] { EVENT_TYPE_PRACTICE };
                    } else if (!containsPractice && containsQualifying && containsRace) {
                        iaEventTypes = new int[] { EVENT_TYPE_RACE, EVENT_TYPE_QUALIFYING };
                    } else {
                        iaEventTypes = new int[] { EVENT_TYPE_RACE, EVENT_TYPE_QUALIFYING, EVENT_TYPE_PRACTICE };
                    }
                    break;
                }
            }
        } else {
            SessionResultSummary.SearchParameters params = new SessionResultSummary.SearchParameters(true, true);
            params.setCustomerId(result1.getDriverResults().get(0).getCustomerId());
            params.setStartDate(new Date(result1.getStartTime().getTime() - 1000));
            params.setEndDate(new Date(result1.getStartTime().getTime() + 1000));
            List<SessionResultSummary> summaries = getSessionResultSummaries(params);
            //            System.err.println("getSessionResult() : found " + summaries.size() + " result summaries");
            for (SessionResultSummary summary : summaries) {
                if (summary.getSubSessionId() == subSessionId) {
                    if (summary.getEventType() == EVENT_TYPE_RACE) {
                        iaEventTypes = new int[] { EVENT_TYPE_RACE, EVENT_TYPE_PRACTICE };
                    } else {
                        // must be either PRACTICE, QUALIFY or TIME_TRIAL (all of which are only a single sub session)
                        iaEventTypes = new int[] { summary.getEventType() };
                    }
                    break;
                }
            }
        }
        SessionResult output = null;
        if (iaEventTypes != null) {
            output = new SessionResult();
            SessionSubResult subResult1 = new SessionSubResult(result1);
            subResult1.setEventTypeId(iaEventTypes[0]);
            subResults.add(subResult1);
            // NOTE: sim session numbers go in reverse chronological order
            if (result1.isHosted()) {
                if (iaEventTypes.length > 1) {
                    simSessionNumber = -1;
                    CsvSessionResult result2 = getSessionResult(subSessionId, simSessionNumber, includeDriverLaps,
                            false);
                    SessionSubResult subResult2 = new SessionSubResult(result2);
                    subResult2.setEventTypeId(iaEventTypes[1]);
                    subResults.add(subResult2);
                }
                if (iaEventTypes.length > 2) {
                    simSessionNumber = -2;
                    CsvSessionResult result3 = getSessionResult(subSessionId, simSessionNumber, includeDriverLaps,
                            false);
                    SessionSubResult subResult3 = new SessionSubResult(result3);
                    subResult3.setEventTypeId(iaEventTypes[2]);
                    subResults.add(subResult3);
                }
            } else {
                if (iaEventTypes.length > 1) {
                    simSessionNumber = -1;
                    CsvSessionResult result2 = getSessionResult(subSessionId, simSessionNumber, includeDriverLaps,
                            false);
                    if (result2 != null) {
                        SessionSubResult subResult2 = new SessionSubResult(result2);
                        subResult2.setEventTypeId(iaEventTypes[1]);
                        subResults.add(subResult2);
                    }
                }
            }
            output.setStartTime(result1.getStartTime());
            output.setSeriesName(result1.getSeriesName());
            output.setTrackName(result1.getTrackName());
            output.setHostedSessionName(result1.getHostedSessionName());
            output.setSubResults(subResults);
        }
        return output;
    }

    private CsvSessionResult getSessionResult(long subSessionId, int simSessionNumber, boolean includeDriverLaps,
            boolean includeSummary) throws IOException, LoginException {
        String sUrl = GET_RACE_RESULT_CSV_URL + "?subsessionid=" + subSessionId + "&simsesnum=" + simSessionNumber
                + "&includeSummary=" + (includeSummary ? "1" : "0");
        String csv = doGetRequest(false, sUrl);
        CsvSessionResult output = CsvSessionResultParser.parse(csv);
        if (includeDriverLaps) {
            for (SessionDriverResult result : output.getDriverResults()) {
                result.setDriverLaps(getSessionDriverLaps(result.getCustomerId(), subSessionId, simSessionNumber));
            }
        }
        return output;
    }

    public SessionResultDriverLaps getSessionDriverLaps(long customerId, long subSessionId, int simSessionNumber)
            throws IOException, LoginException {
        String sUrl = GET_SESSION_DRIVER_LAPS_URL + "?&subsessionid=" + subSessionId + "&custid=" + customerId
                + "&simsesnum=" + simSessionNumber + "&a=null";
        String json = doGetRequest(false, sUrl);
        return SessionResultDriverLapsParser.parse(json);
    }

    public SessionDrivers getLiveSessionDrivers(long subSessionId) throws IOException, LoginException {
        //String sUrl = GET_OPEN_SESSION_DRIVERS_URL + "?subsessionid=" + subSessionId + "&requestindex=0";
        String sUrl = GET_SESSION_DRIVERS_URL + "?subsessionid=" + subSessionId + "&requestindex=0";
        String json = doGetRequest(false, sUrl);
        return SessionDriversParser.parse(json);
    }

    public SessionDrivers getLiveHostedSessionDrivers(long subSessionId, long privateId)
            throws IOException, LoginException {
        String sUrl = GET_HOSTED_SESSION_DRIVERS_URL + "?ssid=" + subSessionId + "&pvtid=" + privateId
                + "&requestindex=0";
        String json = doGetRequest(false, sUrl);
        return SessionDriversParser.parse(json);
    }

    public SeasonStandings getSeasonStandings(SeasonStandings.SearchParameters parameters)
            throws IOException, LoginException {
        SeasonStandingsParser.ListItemHandler lih = new SeasonStandingsParser.ListItemHandler();
        SeasonStandings output = new SeasonStandings();
        output.setTotalRecords(getSeasonStandings(parameters, lih));
        output.setStandings(lih.getList());
        return output;
    }

    public long getSeasonStandings(SeasonStandings.SearchParameters parameters,
            SeasonStandingsParser.ItemHandler handler) throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_SEASON_STANDINGS_URL, parameters.toParameters());
        return SeasonStandingsParser.parse(json, handler);
    }

    public SeasonQualifyingResults getSeasonQualifyingResults(SeasonQualifyingResults.SearchParameters parameters)
            throws IOException, LoginException {
        SeasonQualifyingResultParser.ListItemHandler lih = new SeasonQualifyingResultParser.ListItemHandler();
        SeasonQualifyingResults output = new SeasonQualifyingResults();
        output.setTotalRecords(getSeasonQualifyingResults(parameters, lih));
        output.setResults(lih.getList());
        return output;
    }

    public long getSeasonQualifyingResults(SeasonQualifyingResults.SearchParameters parameters,
            SeasonQualifyingResultParser.ItemHandler handler) throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_SEASON_QUALIFYING_RESULTS_URL, parameters.toParameters());
        return SeasonQualifyingResultParser.parse(json, handler);
    }

    public SeasonTimeTrialStandings getSeasonTimeTrialStandings(
            SeasonTimeTrialStandings.SearchParameters parameters) throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_SEASON_TIME_TRIAL_STANDINGS_URL,
                parameters.toParameters());
        return SeasonTimeTrialStandingsParser.parse(json);
    }

    public TimeTrialResultStandings getTimeTrialResultStandings(
            TimeTrialResultStandings.SearchParameters parameters) throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_TIME_TRIAL_RESULTS_URL, parameters.toParameters());
        return TimeTrialResultStandingsParser.parse(json);
    }

    public List<SpectatorSession> getSpectatorSessions() throws IOException, LoginException {
        String json = doGetRequest(false, GET_SPECTATOR_SESSIONS_URL);
        return SpectatorSessionParser.parse(json);
    }

    public WorldRecords getWorldRecords(WorldRecord.SearchParameters params, boolean includeApiUserTopRow)
            throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_WORLD_RECORDS_URL, params.toParameters());
        return WorldRecordsParser.parse(json, includeApiUserTopRow);
    }

    public DriverStats getDriverStats(DriverStat.SearchParameters params, boolean includeApiUserTopRow)
            throws IOException, LoginException {
        String json = doPostRequestUrlEncoded(false, GET_DRIVER_STATS_URL, params.toParameters());
        return DriverStatsParser.parse(json, includeApiUserTopRow);
    }

    public AccountInfo getAccountInfo() throws IOException, LoginException {
        String html = doGetRequest(false, ACCOUNT_URL);
        return AccountInfoParser.parse(html);
    }

    // TODO: add a function to check if the site is down (i.e. maintenance)
    //       the thread that sends out iRacing PM's should check the output of
    //       this function before trying to send out multiple PM's.
    //       Otherwise, each attempt to send a PM will be futile
    //       Handle cases where the members site is down but forum access is still available

    public String[] getUserData() throws IOException, LoginException {
        //if ((cookie == null) || (cookie.matches("^\\s*$"))) {
        if (!cookieMap.containsKey(JSESSIONID)) {
            //            logger.warn("Cookie is " + cookie + ", performing login to get new cookie");
            if (login() != LoginResponse.Success) {
                System.err.println("Login failed");
                return null;
            }
        }

        URL url = new URL(DRIVER_PROFILE_URL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.addRequestProperty(COOKIE, cookie);

        //        logger.debug("opening connection to " + url.getFile());
        conn.connect();

        if (isMaintenancePage(conn))
            return null;

        String pageData = "";

        String custid = null;
        String friends = null;
        String studied = null;
        BufferedReader b = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        try {
            boolean found = false;
            while (!found && (pageData = b.readLine()) != null) {
                //            logger.debug("line received : " + pageData);
                if (pageData.contains("this.currentCustId = ")) {
                    //                logger.debug("found CustId in this line, attempting extract");
                    int x = pageData.indexOf("this.currentCustId = ");
                    int y = pageData.indexOf(";");
                    custid = pageData.substring(x + 21, y);
                    //                logger.debug("following extract, saving CustId as " + custid);

                    found = true;
                } else if (pageData.contains("var FriendsListing")) {
                    //                logger.debug("found 'var FriendsListing' in this line, attempting extract");
                    int x = pageData.indexOf("{");
                    int y = pageData.indexOf("}");
                    friends = pageData.substring(x + 1, y);
                    friends = friends.replaceAll("\"", "");
                    friends = friends.replaceAll(":1", "");
                    friends = friends.replaceAll(":2", "");
                    //                logger.debug("Friends List (green) is " + friends + " following extract");
                } else {
                    if (!pageData.contains("var WatchedListing")) {
                        continue;
                    }
                    //                logger.debug("found 'var WatchedListing' in this line, attempting extract");
                    int x = pageData.indexOf("{");
                    int y = pageData.indexOf("}");
                    studied = pageData.substring(x + 1, y);
                    studied = studied.replaceAll("\"", "");
                    studied = studied.replaceAll(":1", "");
                    studied = studied.replaceAll(":2", "");
                    //                logger.debug("Watched List (blue) is " + studied + " following extract");
                }

            }
        } finally {
            b.close();
        }
        conn.disconnect();

        return new String[] { custid, friends, studied };
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

        SavingTrustManager(X509TrustManager tm) {
            this.tm = tm;
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            throw new UnsupportedOperationException();
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            this.chain = chain;
            this.tm.checkServerTrusted(chain, authType);
        }
    }
}