package com.jacamars.dsp.rtb.exchanges.google;

import java.io.ByteArrayOutputStream;

import java.io.FileInputStream;

import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode;

import com.google.doubleclick.AdxExt;
import com.google.openrtb.OpenRtb;
import com.google.openrtb.OpenRtb.APIFramework;
import com.google.openrtb.OpenRtb.AdUnitId;
import com.google.openrtb.OpenRtb.BannerAdType;
import com.google.openrtb.OpenRtb.BidRequest.App;
import com.google.openrtb.OpenRtb.BidRequest.Content;
import com.google.openrtb.OpenRtb.BidRequest.Device;
import com.google.openrtb.OpenRtb.BidRequest.Geo;
import com.google.openrtb.OpenRtb.BidRequest.Imp;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Audio;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Banner;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Native;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Pmp;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Pmp.Deal;
import com.google.openrtb.json.OpenRtbJsonFactory;
import com.google.openrtb.json.OpenRtbJsonReader;
import com.google.openrtb.OpenRtb.BidRequest.Imp.Video;
import com.google.openrtb.OpenRtb.BidRequest.Publisher;
import com.google.openrtb.OpenRtb.BidRequest.Site;
import com.google.openrtb.OpenRtb.BidRequest.User;
import com.google.openrtb.OpenRtb.CompanionType;
import com.google.openrtb.OpenRtb.CreativeAttribute;
import com.google.openrtb.OpenRtb.NativeRequest;
import com.google.openrtb.OpenRtb.NativeRequest.Asset;
import com.google.openrtb.OpenRtb.Protocol;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.ProtocolStringList;
import com.jacamars.dsp.rtb.bidder.RTBServer;
import com.jacamars.dsp.rtb.common.Campaign;
import com.jacamars.dsp.rtb.common.Creative;
import com.jacamars.dsp.rtb.exchanges.adx.Base64;
import com.jacamars.dsp.rtb.pojo.BidRequest;
import com.jacamars.dsp.rtb.pojo.Impression;

/**
 * A class that creates a BidRequest from openRTB protobuf that supports most of
 * openRTB.
 * 
 * @author Ben M. Faul
 *
 */

public class GoogleBidRequest extends BidRequest {

	public static byte e_key[];
	public static byte i_key[];

	// Not a bid request, sometimes google returns no bytes on a read.
	private transient boolean notABidRequest = false;
	// The internal JSON form
	private ObjectNode root;
	// The internal protobuf form.
	transient private com.google.openrtb.OpenRtb.BidRequest internal;

	/**
	 * Simple constructor
	 */
	public GoogleBidRequest() {
		impressions = new ArrayList<>();
	}

	/**
	 * Interrogate the bid request
	 */
	@Override
	public Object interrogate(String line) {
		return super.interrogate(line);
	}

	/**
	 * Return the internal protobuf representation
	 * 
	 * @return com.google.openrtb.OpenRtb.BidReques. The internal representation.
	 */
	@JsonIgnore
	public com.google.openrtb.OpenRtb.BidRequest getInternal() {
		return internal;
	}

	/**
	 * Build a protobuf version of the response
	 */
	public GoogleBidResponse buildNewBidResponse(Impression imp, Campaign camp, Creative creat, double price,
			String dealId, int xtime) throws Exception {

		return new GoogleBidResponse(this, imp, camp, creat, id, price, dealId, xtime);
	}

	/**
	 * Return the no bid code.
	 * 
	 * @return int. The return code.
	 */
	@Override
	public int returnNoBidCode() {
		return RTBServer.NOBID_CODE;
	}

	/**
	 * Given the input stream, parse and make the class.
	 * 
	 * @param in InputStream. The content of the POST
	 * @throws Exception on protobuf, json or I/O errors.
	 */
	public GoogleBidRequest(InputStream in) throws Exception {
		int nRead;
		byte[] data = new byte[1024];
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		while ((nRead = in.read(data, 0, data.length)) != -1) {
			buffer.write(data, 0, nRead);
		}

		data = buffer.toByteArray();
		if (data.length == 0) {
			notABidRequest = true;
			return;
		}

		ExtensionRegistry registry = ExtensionRegistry.newInstance();
		registry.add(com.google.doubleclick.AdxExt.imp);
		internal = com.google.openrtb.OpenRtb.BidRequest.parseFrom(data, registry);

		// internal = com.google.openrtb.OpenRtb.BidRequest.parseFrom(in);
		doInternal();
	}

	/**
	 * Given a protobuf Google bid request object, json-ize it. Used for testing.
	 * 
	 * @param br com.google.openrtb.OpenRtb.BidRequest. The bid request in protobuf.
	 * @throws Exception on JSON errors.
	 */
	public GoogleBidRequest(com.google.openrtb.OpenRtb.BidRequest br) throws Exception {
		internal = br;
		doInternal();
	}

	@Override
	public boolean notABidRequest() {
		return notABidRequest;
	}

	/**
	 * Take the internal protobuf and convert to JSON.
	 * 
	 * @throws Exception on JSON or protobuf errors.
	 */
	private void doInternal() throws Exception {
		impressions = new ArrayList<>();
		root = BidRequest.factory.objectNode();

		// Add this to the log
		byte[] bytes = internal.toByteArray();
		String str = new String(Base64.encodeBase64(bytes));
		root.put("protobuf", str);

		root.put("at", internal.getAt().getNumber());
		ProtocolStringList list = internal.getBadvList();
		root.set("badv", getAsStringList(BidRequest.factory.arrayNode(), list));
		if (internal.hasTmax())
			root.put("tmax", internal.getTmax());

		// Google id's can have / in them. That really makes it hard to pass them in
		// pixels and such.
		String id = internal.getId();

		root.put("id", id);
		makeSiteOrApp();
		makeDevice();
		makeImpressions();
		makeUser();

		rootNode = root;
		setup();

	}

	/**
	 * Make a user object
	 */
	private void makeUser() {
		if (!internal.hasUser())
			return;

		User u = internal.getUser();

		ObjectNode node = BidRequest.factory.objectNode();
		if (u.hasBuyeruid())
			node.put("buyeruid", u.getBuyeruid());
		if (u.hasCustomdata())
			node.put("customdata", u.getCustomdata());
		if (u.hasGender())
			node.put("gender", u.getGender());
		if (u.hasGeo())
			addGeo(node, u.getGeo());
		if (u.hasId())
			node.put("id", u.getId());
		if (u.hasKeywords())
			node.put("keywords", u.getKeywords());
		if (u.hasYob())
			node.put("yob", u.getYob());

		root.set("user", node);

	}

	/**
	 * Add the geo object
	 * 
	 * @param node ObjectNode. The parent JSON node
	 * @param g    Geo. The protobuf geo encoding.
	 */
	void addGeo(ObjectNode node, Geo g) {
		ObjectNode geo = BidRequest.factory.objectNode();
		node.set("geo", geo);
		if (g.hasCountry())
			geo.put("country", g.getCountry());
		if (g.hasType())
			geo.put("type", g.getType().getNumber());
		if (g.hasLat())
			geo.put("lat", g.getLat());
		if (g.hasLon())
			geo.put("lon", g.getLon());
		if (g.hasCity())
			geo.put("city", g.getCity());
		if (g.hasRegion())
			geo.put("region", g.getRegion());
		if (g.hasMetro())
			geo.put("metro", g.getMetro());
		if (g.hasUtcoffset())
			geo.put("utcoffset", g.getUtcoffset());
		if (g.hasZip())
			geo.put("zip", g.getZip());
	}

	/**
	 * Make a device object
	 */
	void makeDevice() {

		if (!internal.hasDevice())
			return;

		Device d = internal.getDevice();
		ObjectNode node = BidRequest.factory.objectNode();
		if (d.hasIp())
			node.put("ip", d.getIp());
		if (d.hasLanguage())
			node.put("language", d.getLanguage());
		if (d.hasOs())
			node.put("os", d.getOs());
		if (d.hasOsv())
			node.put("osv", d.getOsv());
		if (d.hasCarrier())
			node.put("carrier", d.getCarrier());
		if (d.hasConnectiontype())
			node.put("connectiontype", d.getConnectiontype().getNumber());
		if (d.hasDidmd5())
			node.put("didmd5", d.getDidmd5());
		if (d.hasDidsha1())
			node.put("didsha1", d.getDidsha1());
		if (d.hasDpidsha1())
			node.put("dpidsha1", d.getDpidsha1());
		if (d.hasDnt())
			node.put("dnt", d.getDnt());
		if (d.hasDevicetype())
			node.put("devicetype", d.getDevicetype().getNumber());
		if (d.hasUa())
			node.put("ua", d.getUa());
		if (d.hasJs())
			node.put("js", d.getJs());
		if (internal.getDevice().hasGeo())
			addGeo(node, d.getGeo());
		root.set("device", node);

	}

	/**
	 * Make either the site or app object.
	 */
	void makeSiteOrApp() throws Exception {
		ObjectNode node = BidRequest.factory.objectNode();
		if (internal.hasSite()) {
			Site s = internal.getSite();
			root.set("site", node);

			if (s.hasId())
				node.put("id", s.getId());
			if (s.hasName())
				node.put("name", s.getName());
			node.set("cat", getAsStringList(BidRequest.factory.arrayNode(), s.getCatList()));
			if (s.hasKeywords())
				node.put("keywords", s.getKeywords());
			if (s.hasMobile()) {
				if (s.getMobile())
					node.put("mobile", 1);
				else
					node.put("mobile", 0);
			}

			if (s.hasDomain())
				node.put("domain", s.getDomain());

			if (s.hasPage()) {
				node.put("page", s.getPage());
				/**
				 * If the domain is empty, fake it out.
				 */
				if (!s.hasDomain()) {
					String value = s.getPage();
					value = URLDecoder.decode(value, java.nio.charset.StandardCharsets.UTF_8.toString());
					value = value.replace("https://", "");
					value = value.replace("http://", "");
					value = value.replace("www.", "");
					int i = value.indexOf("/");
					if (i != -1)
						value = value.substring(0, i);
					node.put("domain", value);
				}
			}

			if (s.hasRef())
				node.put("ref", s.getRef());
			if (s.hasSearch())
				node.put("search", s.getSearch());
			if (s.hasPrivacypolicy())
				node.put("privacypolicy", s.getPrivacypolicy());
			if (s.hasPublisher()) {
				Publisher p = s.getPublisher();
				ObjectNode pub = BidRequest.factory.objectNode();
				pub.put("id", p.getId());
				pub.put("name", p.getId());
				if (p.hasDomain())
					pub.put("domain", p.getDomain());
				node.set("publisher", pub);
			}
		} else {
			App a = internal.getApp();
			root.set("app", node);
			if (a.hasId())
				node.put("id", a.getId());
			if (a.hasName())
				node.put("name", a.getName());
			node.set("cat", getAsStringList(BidRequest.factory.arrayNode(), a.getCatList()));

			if (a.hasKeywords())
				node.put("keywords", a.getKeywords());
			if (a.hasContent()) {
				Content c = a.getContent();
				ObjectNode content = BidRequest.factory.objectNode();
				node.set("content", content);
				if (c.hasAlbum())
					content.put("album", c.getAlbum());
				if (c.hasArtist())
					content.put("artist", c.getArtist());
				if (c.hasContentrating())
					content.put("contentrating", c.getContentrating());
				if (c.hasContext22())
					content.put("context22", c.getContext22());
				if (c.hasEmbeddable())
					content.put("embeddable", c.getEmbeddable());
				if (c.hasEpisode())
					content.put("episode", c.getEpisode());
				if (c.hasSourcerelationship())
					content.put("src", c.getSourcerelationship());
				if (c.hasGenre())
					content.put("genre", c.getGenre());
				if (c.hasIsrc())
					content.put("isrc", c.getIsrc());
				if (c.hasKeywords())
					content.put("keyword", c.getKeywords());
				if (c.hasLanguage())
					content.put("language", c.getLanguage());
				if (c.hasLen())
					content.put("len", c.getLen());
				if (c.hasLivestream())
					content.put("livestream", c.getLivestream());
				content.set("cat", getAsStringList(BidRequest.factory.arrayNode(), c.getCatList()));
				if (c.hasSeason())
					content.put("season", c.getSeason());
				if (c.hasSeries())
					content.put("series", c.getSeries());
				if (c.hasTitle())
					content.put("title", c.hasTitle());
				if (c.hasUrl())
					content.put("url", c.getUrl());
				if (c.hasUserrating())
					content.put("userrating", c.getUserrating());
			}
			if (a.hasBundle())
				node.put("bundle", internal.getApp().getBundle());
			if (a.hasDomain())
				node.put("domain", internal.getApp().getDomain());
			node.set("cat", getAsStringList(BidRequest.factory.arrayNode(), internal.getApp().getCatList()));
			if (a.hasPrivacypolicy())
				node.put("privacypolicy", internal.getApp().getPrivacypolicy());
			if (internal.getApp().hasPublisher()) {
				Publisher p = a.getPublisher();
				ObjectNode pub = BidRequest.factory.objectNode();
				pub.put("id", internal.getApp().getPublisher().getId());
				pub.put("name", internal.getApp().getPublisher().getId());
				if (p.hasDomain())
					pub.put("domain", p.getDomain());
				node.set("publisher", pub);
			}
		}

		/**
		 * Handle vertical segments, place in app.cat or site.cat
		 */
		if (internal.hasUser()) {
			User u = internal.getUser();
			if (u.getDataCount() > 0) {
				int k = u.getDataCount();
				for (int i = 0; i < k; i++) {
					OpenRtb.BidRequest.Data d = u.getData(i);
					int z = d.getSegmentCount();
					ArrayNode cat = (ArrayNode) node.get("cat");
					if (z > 0) {
						for (int j = 0; j < z; j++) {
							OpenRtb.BidRequest.Data.Segment s = d.getSegment(j);
							String id = s.getId();
							cat.add(id);
						}
					}
				}
			}
		}

	}

	/**
	 * Make JSON based impressions.
	 */
	void makeImpressions() {
		ArrayNode array = BidRequest.factory.arrayNode();
		root.set("imp", array);
		for (int i = 0; i < internal.getImpCount(); i++) {
			Imp imp = internal.getImp(i);
			ObjectNode impx = null;

			if (imp.hasBanner()) {
				impx = doBanner(array, imp.getBanner(), i);
			} else if (imp.hasVideo()) {
				impx = doVideo(array, imp.getVideo(), i);
			} else if (imp.hasNative()) {
				impx = doNative(array, imp.getNative(), i);
			} else if (imp.hasAudio()) {
				impx = doAudio(array, imp.getAudio(), i);
			}

			if (imp.hasClickbrowser())
				impx.put("clickbrowser", imp.getClickbrowser());
			if (imp.hasInstl()) {
				boolean t = imp.getInstl();
				if (t)
					impx.put("instl", 1);
				else
					impx.put("instl", 0);
			}
			if (imp.hasBidfloor())
				impx.put("bidfloor", imp.getBidfloor());
			if (imp.hasBidfloorcur())
				impx.put("bidfloorcur", imp.getBidfloorcur());
			if (imp.hasDisplaymanager())
				impx.put("displaymanager", imp.getDisplaymanager());
			if (imp.hasTagid())
				impx.put("tagid", imp.getTagid());
			if (imp.getExtension(AdxExt.imp).getBillingIdCount() > 0) {
				ObjectNode ext = BidRequest.factory.objectNode();
				ext.set("blid", getAsBillingIdList(BidRequest.factory.arrayNode(),
						imp.getExtension(AdxExt.imp).getBillingIdList()));
				impx.set("ext", ext);
			}

			if (imp.hasPmp())
				doPmp(imp.getPmp(), impx);
		}
	}

	/**
	 * Make a list of deals
	 * 
	 * @param pmp  Pmp. Private market place on protobuf.
	 * @param impx ObjectNode. The JSON equivalent.
	 */
	void doPmp(Pmp pmp, ObjectNode impx) {
		ObjectNode node = BidRequest.factory.objectNode();
		ArrayNode lx = BidRequest.factory.arrayNode();
		impx.set("pmp", node);

		if (pmp.hasPrivateAuction())
			node.put("private_auction", pmp.getPrivateAuction());
		else
			node.put("private_auction", 0);

		if (pmp.getDealsCount() > 0) {
			List<Deal> list = pmp.getDealsList();
			for (int i = 0; i < list.size(); i++) {
				ObjectNode d = BidRequest.factory.objectNode();
				Deal deal = list.get(i);
				if (deal.hasAt())
					d.put("at", deal.getAt().getNumber());
				if (deal.hasBidfloor())
					d.put("bidfloor", deal.getBidfloor());
				if (deal.hasBidfloorcur())
					d.put("bidfloorcur", deal.getBidfloorcur());
				d.put("id", deal.getId());
				lx.add(d);
			}
		}
		node.set("deals", lx);
	}

	ObjectNode doAudio(ArrayNode array, Audio aud, int i) {
		ObjectNode node = BidRequest.factory.objectNode();
		ObjectNode audio = BidRequest.factory.objectNode();
		audio.set("audio", node);

		node.put("id", Integer.toString(i + 1));
		if (aud.getStitched())
			audio.put("stitched", aud.getStitched());
		ArrayNode a = BidRequest.factory.arrayNode();
		node.set("api", getAsAttributeListAPI(a, aud.getApiList()));
		if (aud.getBattrCount() > 0) {
			a = BidRequest.factory.arrayNode();
			node.set("battr", getAsAttributeList(a, aud.getBattrList()));
		}
		if (aud.getCompanionadCount() > 0) {
			List<Banner> list = aud.getCompanionadList();
			ArrayNode arr = BidRequest.factory.arrayNode();
			for (int k = 0; i < list.size(); k++) {
				Banner b = list.get(k);
				doBanner(arr, b, k);
			}
			node.set("companionad", arr);

		}
		if (aud.getCompaniontypeCount() > 0) {
			List<CompanionType> clist = aud.getCompaniontypeList();
			ArrayNode arx = BidRequest.factory.arrayNode();
			getAsCompanionTypeList(arx, clist);
			node.set("companiontype", arx);
		}
		node.put("feed", aud.hasFeed());
		if (aud.hasMaxbitrate())
			node.put("maxbidrate", aud.getMaxbitrate());
		if (aud.hasMaxduration())
			node.put("maxduration", aud.getMaxduration());
		if (aud.hasMinbitrate())
			node.put("minduration", aud.getMinduration());
		if (aud.hasMaxextended())
			node.put("maxextended", aud.getMaxextended());
		if (aud.hasMaxseq())
			node.put("maxseq", aud.getMaxseq());
		if (aud.hasNvol()) {

		}
		if (aud.hasSequence())
			node.put("sequence", aud.getSequence());
		if (aud.hasStartdelay())
			node.put("startdelay", aud.getStartdelay());

		array.add(audio);
		return audio;
	}

	/**
	 * Make a banner impression.
	 * 
	 * @param array ArrayNode. Holds the impressions.
	 * @param b     Banner. The protobuf banner.
	 * @return ObjectNode. The banner as JSON.
	 */
	ObjectNode doBanner(ArrayNode array, Banner b, int i) {
		ObjectNode node = BidRequest.factory.objectNode();
		ObjectNode banner = BidRequest.factory.objectNode();
		banner.set("banner", node);

		banner.put("id", Integer.toString((i + 1)));

		if (b.hasH())
			node.put("h", b.getH());
		// if (b.hasHmax()) node.put("hmax",b.getHmax());
		// if (b.hasHmin()) node.put("hmin", b.getHmin());
		if (b.hasPos())
			node.put("pos", b.getPos().getNumber());
		if (b.hasTopframe())
			node.put("topframe", b.getTopframe());
		if (b.hasW())
			node.put("w", b.getW());
		// if (b.hasWmax()) node.put("wmax",b.getWmax());
		// if (b.hasWmin()) node.put("wmin", b.getWmin());
		if (b.getBattrCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("battr", getAsAttributeList(a, b.getBattrList()));
		}
		if (b.getApiCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("api", getAsAttributeListAPI(a, b.getApiList()));
		}
		if (b.getBtypeCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("btype", getAsAttributeListBanner(a, b.getBtypeList()));
		}
		if (b.getMimesCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("mimes", getAsStringList(a, b.getMimesList()));
		}

		/**
		 * If the protobuf has a format object, then create the JSON equivalent
		 */
		if (0 != b.getFormatCount()) {
			List<Banner.Format> formats = b.getFormatList();
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("format", a);
			for (int j = 0; j < formats.size(); j++) {
				ObjectNode n = BidRequest.factory.objectNode();
				Banner.Format format = formats.get(j);
				n.put("w", format.getW());
				n.put("h", format.getH());
				a.add(n);
			}
		}

		array.add(banner);
		return banner;
	}

	/**
	 * Make a video impression.
	 * 
	 * @param array ArrayNode. Holds the impressions.
	 * @param v     Video. The protobuf video.
	 * @param i     int. The index to assign.
	 * @return ObjectNode. The video as JSON.
	 */
	static ObjectNode doVideo(ArrayNode array, Video v, int i) {
		ObjectNode node = BidRequest.factory.objectNode();
		ObjectNode video = BidRequest.factory.objectNode();
		video.set("video", node);

		String impid = Integer.toString(i + 1);
		video.put("id", impid);

		if (v.hasH())
			node.put("h", v.getH());
		if (v.hasW())
			node.put("w", v.getW());
		if (v.hasPos())
			node.put("pos", v.getPos().getNumber());
		if (v.getApiCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("api", getAsAttributeListAPI(a, v.getApiList()));
		}
		if (v.getBattrCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("battr", getAsAttributeList(a, v.getBattrList()));
		}
		if (v.getMimesCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("mimes", getAsStringList(a, v.getMimesList()));
		}

		// if (v.hasProtocol()) video.put("protocol", v.getProtocol().getNumber());
		if (v.getProtocolsCount() > 0) {
			ArrayNode a = BidRequest.factory.arrayNode();
			node.set("protocols", getAsAttributeListProtocols(a, v.getProtocolsList()));
		}

		if (v.hasBoxingallowed())
			node.put("boxingallowed", v.getBoxingallowed());
		if (v.hasLinearity())
			node.put("linearity", v.getLinearity().getNumber());
		if (v.hasMaxbitrate())
			node.put("maxbitrate", v.getMaxbitrate());
		if (v.hasMinbitrate())
			node.put("minbitrate", v.getMinbitrate());
		if (v.hasMinduration())
			node.put("minduration", v.getMinduration());
		if (v.hasMaxduration())
			node.put("maxduration", v.getMaxduration());
		if (v.hasMaxextended())
			node.put("maxextended", v.getMaxextended());

		array.add(video);
		return video;
	}

	static ObjectNode doNative(ArrayNode array, Native n, int i) {
		ObjectNode node = BidRequest.factory.objectNode();
		ObjectNode nat = BidRequest.factory.objectNode();

		nat.set("native", node);
		if (n.hasRequest())
			node.put("request", n.getRequest());
		if (n.hasVer())
			node.put("ver", n.getVer());

		ArrayNode a = BidRequest.factory.arrayNode();
		node.set("api", getAsAttributeListAPI(a, n.getApiList()));
		ArrayNode b = BidRequest.factory.arrayNode();
		node.set("battr", getAsAttributeList(b, n.getBattrList()));
		if (n.hasRequestNative()) {
			NativeRequest nr = n.getRequestNative();
			if (nr.hasContext()) {
				node.put("context", nr.getContext().getNumber());
			}
			if (nr.hasContextsubtype()) {
				node.put("contextsubtype", nr.getContextsubtype().getNumber());
			}
			if (nr.hasPlcmtcnt()) {
				node.put("plcmttype", nr.getPlcmtcnt());
			}
			if (nr.hasPlcmttype()) {
				node.put("plcmttype", nr.getPlcmttype().ordinal());
			}
			if (nr.hasAdunit()) {
				AdUnitId au = nr.getAdunit();
				node.put("adunit", au.getNumber());
			}
			if (nr.hasSeq()) {
				node.put("seq", nr.getSeq());
			}

			List<Asset> list = nr.getAssetsList();
			a = BidRequest.factory.arrayNode();
			node.set("assets", a);
			for (Asset asset : list) {
				NativeFramework.makeAsset(asset, a);
			}

		}
		array.add(nat);
		return nat;
	}

	/**
	 * Return a list of creative attributes in JSON form
	 * 
	 * @param node ArrayNode. The node we will add to.
	 * @param list List. A list of creative attributes.
	 * @return ArrayNode. The node we passed in.
	 */
	static ArrayNode getAsAttributeList(ArrayNode node, List<CreativeAttribute> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i).getNumber());
		}
		return node;
	}

	/**
	 * Return a list of API frameworks in JSON form
	 * 
	 * @param node ArrayNode. The node we will add to.
	 * @param list List. A list of API frameworks.
	 * @return ArrayNode. The node we passed in.
	 */
	static ArrayNode getAsAttributeListAPI(ArrayNode node, List<APIFramework> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i).getNumber());
		}
		return node;
	}

	/**
	 * Return a list of Banner Ad types in JSON form
	 * 
	 * @param node ArrayNode. The node we will add to.
	 * @param list List. A list of banner ad types.
	 * @return ArrayNode. The node we passed in.
	 */
	static ArrayNode getAsAttributeListBanner(ArrayNode node, List<BannerAdType> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i).getNumber());
		}
		return node;
	}

	/**
	 * Return a list of protocol numbers in JSON form
	 * 
	 * @param node ArrayNode. The node we will add to.
	 * @param list List. A list of protocol numbers.
	 * @return ArrayNode. The node we passed in.
	 */
	static ArrayNode getAsAttributeListProtocols(ArrayNode node, List<Protocol> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i).getNumber());
		}
		return node;
	}

	/**
	 * Return a list of protocol strings in JSON form
	 * 
	 * @param node ArrayNode. The node we will add to.
	 * @param list List. A list of protocol strings.
	 * @return ArrayNode. The node we passed in.
	 */
	protected static ArrayNode getAsStringList(ArrayNode node, ProtocolStringList list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i));
		}
		return node;
	}

	/**
	 * Add the list of companiontypes to the arraynode.
	 * 
	 * @param node ArrayNode. The list to add to.
	 * @param list List. The list of companiontypes.
	 * @return The completed list.
	 */
	protected static ArrayNode getAsCompanionTypeList(ArrayNode node, List<CompanionType> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i).getNumber());
		}
		return node;
	}

	protected static ArrayNode getAsBillingIdList(ArrayNode node, List<Long> list) {
		for (int i = 0; i < list.size(); i++) {
			node.add(list.get(i));
		}
		return node;
	}

	/**
	 * The configuration requires an e_key and an i_key
	 */
	@Override
	public void handleConfigExtensions(Map extension) {
		String ekey = (String) extension.get("e_key");
		String ikey = (String) extension.get("i_key");
		if (ekey == null || ekey.length() == 0) {
			logger.warn("Google ekey and ikeys are not set");
			return;
		}
		GoogleWinObject.encryptionKeyBytes = e_key = javax.xml.bind.DatatypeConverter.parseBase64Binary(ekey);
		GoogleWinObject.integrityKeyBytes = i_key = javax.xml.bind.DatatypeConverter.parseBase64Binary(ikey);
	}

	/**
	 * Makes sure the Google billing_id is available on the creative
	 * 
	 * @param creat       Creative. The creative in question.
	 * @param errorString StringBuilder. The error handling string. Add your error
	 *                    here if not null.
	 * @returns boolean. Returns true if the Exchange and creative are compatible.
	 */
	@Override
	public boolean checkNonStandard(Creative creat, StringBuilder errorString) {
		if (creat.extensions == null || creat.extensions.get("billing_id") == null) {
			if (errorString != null) {
				errorString.append(creat.impid);
				errorString.append(" ");
				errorString.append("Missing extensions for Google");
			}
			return false;
		}
		return true;
	}

	public static GoogleBidRequest fromRTBFile(String initialFile) throws Exception {
		InputStream targetStream = new FileInputStream(initialFile);
		OpenRtbJsonFactory jf = OpenRtbJsonFactory.create();

		MyReader reader = new MyReader(jf);
		com.google.openrtb.OpenRtb.BidRequest r = reader.readBidRequest(targetStream);

		GoogleBidRequest google = new GoogleBidRequest(r);

		return google;
	}

	@Override
	public GoogleBidRequest copy(InputStream in) throws Exception {
		GoogleBidRequest copy = new GoogleBidRequest(in);
		copy.usesEncodedAdm = usesEncodedAdm;
		return copy;
	}
}

class MyReader extends OpenRtbJsonReader {

	protected MyReader(OpenRtbJsonFactory factory) {
		super(factory);
		// TODO Auto-generated constructor stub
	}

}