/*
 * Copyright 2014 - 2017 Cognizant Technology Solutions
 *
 * 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 com.cognizant.cognizantits.engine.reporting.performance.har;

import com.cognizant.cognizantits.engine.reporting.performance.PerformanceTimings;
import com.cognizant.cognizantits.engine.reporting.performance.ResourceTimings;
import com.google.gson.Gson;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;

/**
 * helper class for har resource entry
 *
 * 
 * 
 * @see
 * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html#sec-object-types-entries
 */
@SuppressWarnings("unchecked")
public final class Entry extends JSONObject {

    private static final Logger LOG = Logger.getLogger(com.cognizant.cognizantits.engine.reporting.performance.har.Entry.class.getName());
    private static final long serialVersionUID = 1L;
    private static final String DF = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
    ResourceTimings e;
    Request req;
    Response res;
    Timings t;

    public Entry(String resourceTimings, Page p) {
        super();
        Gson gson = new Gson();
        e = gson.fromJson(resourceTimings, ResourceTimings.class);
        create(p);
    }
   
    public Entry(Page p) {
        super();
        e = p.pt.toResourceTimings();
        create(p);
    }

    protected void create(Page p1) {
        try {
            put("startedDateTime", getMillstoDate(Math.round(p1.pt.navigationStart + e.startTime)));
            put("pageref", p1.getID());
            put("cache", Prop.getEmpty());
            req = new Request(e);
            put("request", req);
            res = new Response(e);
            put("response", res);
            t = new Timings(p1.pt, e);
            put("timings", t);
            put("time", processed(t.duration));
        } catch (Exception ex) {
            LOG.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    protected long processed(Double v) {
        return Math.round(v);
    }

    public static String getMillstoDate(long mills) {
        SimpleDateFormat df = new SimpleDateFormat(DF);
        return df.format(new Date(mills));
    }

    public void addDef(JSONObject obj, boolean isReq) {
        obj.put("httpVersion", "HTTP/1.x");
        obj.put("headers", getHeaders(isReq));
        obj.put("cookies", Prop.getEmptyArray());
        obj.put("headersSize", -1);
        obj.put("bodySize", -1);
    }

    
	public JSONArray getHeaders(boolean isReq) {

        JSONArray hs = Prop.getEmptyArray();
        if (!isReq) {
            hs.add(getHeader("Content-Type", e.mimeType()));
        }
        return hs;
    }

    public JSONObject getHeader(String name, String value) {
        JSONObject h = new JSONObject();
        h.put("name", name);
        h.put("value", value);
        return h;
    }

    final class Request extends JSONObject {

        private static final long serialVersionUID = 1L;

        
        public Request(ResourceTimings e) {
            put("method", "GET");
            put("url", e.name);
            addDef((JSONObject)this, true);
            put("queryString", getParams(e));
        }

        public JSONArray getParams(ResourceTimings e) {
            JSONArray paramList = new JSONArray();
            try {
                List<NameValuePair> params = URLEncodedUtils.parse(new URI(e.name), "UTF-8");
                for (NameValuePair pair : params) {
                    JSONObject jsonPair = new JSONObject();
                    jsonPair.put("name", pair.getName());
                    jsonPair.put("value", pair.getValue());
                    paramList.add(jsonPair);
                }

            } catch (Exception ex) {
                return paramList;
            }
            return paramList;
        }
    }

    class Response extends JSONObject {

        private static final long serialVersionUID = 1L;

        
        private Response(ResourceTimings e) {
            put("status", 200);
            put("statusText", "OK");
            put("_transferSize", -1);
            put("redirectURL", "");
            put("content", new Content(e));
            addDef((JSONObject)this, false);
        }

        class Content extends JSONObject {

            private static final long serialVersionUID = 1L;

            /**
             * size not supported (by browsers) as of Dec 2015 (in draft)
             *
             * @param e
             */
           
            public Content(ResourceTimings e) {
                put("size", 0);
                put("mimeType", e.mimeType());
                put("compression", 0);
            }
        }

    }

    final class Timings extends JSONObject {

        private static final long serialVersionUID = 1L;
        public Double duration;

       
        public Timings(PerformanceTimings pt, ResourceTimings e) {
            Integer na = -1;
            Double blocked = Math.max(0, e.connectStart - e.startTime);
            
            Double dns = (e.domainLookupEnd - e.domainLookupStart) == 0 ? na
                    : (e.domainLookupEnd - e.domainLookupStart);

            Double connect = (e.connectEnd - e.connectStart) == 0 ? na
                    : (e.connectEnd - e.connectStart);

            Double send = Math.max(0, e.responseStart - e.requestStart);

            Double receive = Math.max(0, e.responseEnd - e.responseStart);
            Double ssl = -1d;
            try {
                // calc ssl only if req is secure connection
                if (e.name != null && e.name.toLowerCase().startsWith("https:")) {
                    ssl = Math.round(e.secureConnectionStart) == 0 ? na
                            : (e.connectEnd - e.secureConnectionStart);
                }
            } catch (Exception ex) {
            }
            Double time = e.duration;
            //calculate wait time from time unaccunted (avoids bizzare graph)
            long wait = rnd(time) - rnd(nonNtve(dns)) - rnd(nonNtve(connect)) - rnd(nonNtve(ssl))
                    - rnd(send) - rnd(receive) - rnd(blocked);
            if (wait < 0) {
                time -= wait;
                wait = 0;
            }

            duration = time;

            put("blocked", rnd(blocked));
            put("dns", rnd(dns));
            put("connect", rnd(connect));
            put("send", rnd(send));
            put("wait", wait);
            put("receive", rnd(receive));
            put("ssl", rnd(ssl));

        }

        protected double nonNtve(Double d) {
            return Math.max(0, d);
        }

        protected long rnd(Double v) {
            return Math.round(v);
        }
    }
}