/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.simulation;

import com.sun.electric.database.geometry.btree.BTree;
import com.sun.electric.database.geometry.btree.CachingPageStorage;
import com.sun.electric.database.geometry.btree.CachingPageStorageWrapper;
import com.sun.electric.database.geometry.btree.FilePageStorage;
import com.sun.electric.database.geometry.btree.MemoryPageStorage;
import com.sun.electric.database.geometry.btree.OverflowPageStorage;
import com.sun.electric.database.geometry.btree.PageStorage;
import com.sun.electric.database.geometry.btree.unboxed.AssociativeCommutativeOperation;
import com.sun.electric.database.geometry.btree.unboxed.LatticeOperation;
import com.sun.electric.database.geometry.btree.unboxed.Pair;
import com.sun.electric.database.geometry.btree.unboxed.Unboxed;
import com.sun.electric.database.geometry.btree.unboxed.UnboxedHalfDouble;
import com.sun.electric.database.geometry.btree.unboxed.UnboxedPair;

abstract class BTreeSignal<S extends Sample> extends MutableSignal<S>
{
	private Signal.View<S> exactView = null;
	private final BTree<Double,S,Pair<S,S>> tree;
	private double minTime = Double.MAX_VALUE, maxTime = -Double.MAX_VALUE;
	private double minValue = Double.MAX_VALUE, maxValue = -Double.MAX_VALUE;
	public static int misses = 0;
	public static int steps = 0;
	public static int numLookups = 0;

	public BTreeSignal(SignalCollection sc, Stimuli sd, String signalName, String signalContext,
		boolean digital, BTree<Double,S,Pair<S,S>> tree)
	{
		super(sc, sd, signalName, signalContext, digital);
		if (tree == null) throw new RuntimeException();
		this.tree = tree;
		this.exactView = new Signal.View<S>()
		{
			public int getNumEvents() { return BTreeSignal.this.tree.size(); }
			public double getTime(int index)
			{
				Double d = BTreeSignal.this.tree.getKeyFromOrd(index);
				if (d == null)
					throw new RuntimeException("Entry " + index + " not valid (tree size is " +
						BTreeSignal.this.tree.size() + ")");
				return d.doubleValue();
			}
			public S getSample(int index)
			{
				S ret = BTreeSignal.this.tree.getValFromOrd(index);
				if (ret == null)
					throw new RuntimeException("Entry " + index + " not valid (tree size is " +
						BTreeSignal.this.tree.size() + ")");
				return ret;
			}
		};
	}

	public S getSample(double time) { return tree.getValFromKey(new Double(time)); }

	public void addSample(double time, S sample)
	{
		tree.insert(new Double(time), sample);
		minTime = Math.min(minTime, time);
		maxTime = Math.max(maxTime, time);
		minValue = Math.min(minValue, sample.getMinValue());
		maxValue = Math.max(maxValue, sample.getMaxValue());
	}

	public void replaceSample(double time, S sample)
	{
		tree.replace(new Double(time), sample);
		minTime = Math.min(minTime, time);
		maxTime = Math.max(maxTime, time);
		minValue = Math.min(minValue, sample.getMinValue());
		maxValue = Math.max(maxValue, sample.getMaxValue());
	}

	public Signal.View<S> getExactView() { return exactView; }

	public Signal.View<RangeSample<S>> getRasterView(double t0, double t1, int numPixels)
	{
		return new BTreeRasterView(t0, t1, numPixels);
	}

	public boolean isEmpty() { return tree.size()==0; }

	public double getMinTime() { return minTime; }

	public double getMaxTime() { return maxTime; }

	public double getMinValue() { return minValue; }

	public double getMaxValue() { return maxValue; }

	protected Pair<S,S> getSummaryFromKeys(Double t1, Double t2)
	{
		return tree.getSummaryFromKeys(t1, t2);
	}

	/**
	 *  When a raster view is requested, there are two possibilities:
	 *  the signal has at least numRegions samples between t0 and t1,
	 *  or it has less than numRegions samples.  In the latter case we
	 *  use "exact" mode and snap the raster samples to the actual
	 *  samples.  In the former case we use range queries to summarize
	 *  multiple actual samples in each raster sample.
	 */
	private class BTreeRasterView implements Signal.View<RangeSample<S>>
	{
		private final double t0, t1;
		private final int numRegions;
		private final boolean exact;
		private int t0_ord, t1_ord;

		public BTreeRasterView(double t0, double t1, int numRegions)
		{
			Double t0_ = new Double(Math.min(t0, t1));
			Double t1_ = new Double(Math.max(t0, t1));
			t0_ord = tree.getOrdFromKeyFloor(t0_);
			t1_ord = tree.getOrdFromKeyFloor(t1_);

			// "snap" t0 and t1 to the nearest actual sample strictly outside the viewfinder
			t0_ = tree.getKeyFromOrd(t0_ord);
			t1_ord = Math.min(tree.size()-1, t1_ord+1);
			t1_ = tree.getKeyFromOrd(t1_ord);

			t0_ord = Math.max(t0_ord, 0);
			t1_ord = Math.min(t1_ord, tree.size()-1);

			this.t0 = t0_.doubleValue();
			this.t1 = t1_.doubleValue();

			// There is a bug in the BTree code which produces inaccurate sampling if the actual number
			// of samples is close, but slightly greater than the requested number.  So, for example,
			// if there are 600 actual samples (t1_ord - t2_ord+1 == 600) and the user requests 590
			// samples (numRegions=590) then the sampling will be bad.
			// The solution is to increase the requested number of samples if it is within 75% of the
			// actual number of samples
			int actualNumSamples = t1_ord - t0_ord + 1;
			if (actualNumSamples > numRegions && actualNumSamples*0.75 < numRegions)
				numRegions = actualNumSamples + 1;

			this.exact = numRegions > actualNumSamples;
			this.numRegions = exact ? actualNumSamples : numRegions;
		}

		public int getNumEvents() { return numRegions; }

		public double getTime(int index)
		{
			if (index < 0)
				throw new RuntimeException("ERROR: getTime() called with negative number");
			if (index >= getNumEvents())
				throw new RuntimeException("ERROR: getTime() called with number greater than or equal to getNumEvents()");
			if (!exact) return t0+(((t1-t0)*index)/numRegions);
			Double ret = tree.getKeyFromOrd(t0_ord + index);
			if (ret == null)
				throw new RuntimeException("ERROR: sample not found in BTree -- this should not happen;"+
					" t0_ord="+t0_ord+" t1_ord="+t1_ord+
					" exact="+exact+" index="+index+" tree.size()="+tree.size()+
					" numRegions="+numRegions);
			return ret.doubleValue();
		}

		public RangeSample<S> getSample(int index)
		{
			if (index >= getNumEvents()-1)
			{
				S sample = tree.getValFromOrd(t1_ord);
				return sample == null ? null : new RangeSample<S>(sample, sample);
			}
			if (exact)
			{
				S sample = tree.getValFromOrd(t0_ord+index);
				return sample == null ? null : new RangeSample<S>(sample, sample);
			}
			Double tfirst = new Double(getTime(index));
			Double tsecond = new Double(getTime(index+1));
			if (tfirst.doubleValue() == tsecond.doubleValue())
			{
				// this case can occur if the signal's samples
				// aren't evenly spaced; in effect we end up
				// acting sort of like exact mode at times.
				S sample = tree.getValFromKey(tfirst);
				return sample == null ? null : new RangeSample<S>(sample, sample);
			} else
			{
				Pair<S,S> highlow = tree.getSummaryFromKeys(tfirst, tsecond);
				return highlow == null
					? null
					: new RangeSample<S>(highlow.getKey(), highlow.getValue());
			}
		}
	}

	// Page Storage //////////////////////////////////////////////////////////////////////////////

//	private static CachingPageStorage ps = null;

	static <SS extends Sample> BTree<Double,SS,Pair<SS,SS>> getTree(Unboxed<SS> unboxer, LatticeOperation<SS> latticeOp, Stimuli sd)
	{
//		if (ps == null)
//			try
//			{
//				long highWaterMarkInBytes = 50 * 1024 * 1024;
//				PageStorage fps = FilePageStorage.create();
//				PageStorage ops = new OverflowPageStorage(new MemoryPageStorage(fps.getPageSize()), fps, highWaterMarkInBytes);
//				ps = new CachingPageStorageWrapper(ops, 16 * 1024, false);
//			} catch (Exception e) {
//				throw new RuntimeException(e);
//			}
		CachingPageStorage ps = sd.getPageStorage();
		return new BTree<Double,SS,Pair<SS,SS>>(ps, UnboxedHalfDouble.instance, unboxer,
			 new Summary<SS>(UnboxedHalfDouble.instance, unboxer, latticeOp));
	}

	private static class Summary<SS extends Sample> extends UnboxedPair<SS,SS>
		implements BTree.Summary<Double,SS,Pair<SS,SS>>, AssociativeCommutativeOperation<Pair<SS,SS>>
	{
		private final LatticeOperation<SS> latticeOp;
		private final Unboxed<Double> uk;
		private final Unboxed<SS> uv;

		public Summary(Unboxed<Double> uk, Unboxed<SS> uv, LatticeOperation<SS> latticeOp)
		{
			super(uv,uv);
			this.uk = uk;
			this.uv = uv;
			this.latticeOp = latticeOp;
		}

		public void call(byte[] buf_arg, int ofs_arg, byte[] buf_result, int ofs_result)
		{
			System.arraycopy(buf_arg, ofs_arg+uk.getSize(), buf_result, ofs_result, uv.getSize());
			System.arraycopy(buf_arg, ofs_arg+uk.getSize(), buf_result, ofs_result+uv.getSize(), uv.getSize());
		}

		public void multiply(byte[] buf1, int ofs1, byte[] buf2, int ofs2, byte[] buf_dest, int ofs_dest)
		{
			latticeOp.glb(buf1, ofs1, buf2, ofs2, buf_dest, ofs_dest);
			latticeOp.lub(buf1, ofs1+uv.getSize(), buf2, ofs2+uv.getSize(), buf_dest, ofs_dest+uv.getSize());
		}
	}
}
