package hep.lcd.geometry;
import java.util.*;
import hep.lcd.geometry.component.*;

/**
 * A CalorimeterCell which deals with the new tower id definition
 */
public class CalorimeterCellNewTag extends AbstractCalorimeterCell
{
	public CalorimeterCellNewTag(Detector det)
	{
		this.id = 0;
		this.det = det;
	}
	public CalorimeterCellNewTag(Detector det,int id)
	{
		this.det = det;
		this.id = id;
	}
        public int findTowerIDfromCartesian(double x,double y,double z)
        {
           double r = Math.sqrt(x*x + y*y);
           double theta = Math.atan2(r,z);
           double phi = Math.atan2(y,x);
           if(phi < 0.)phi += 2.*Math.PI;
           return constructID(r,z,theta,phi);
        }
        public int findTowerIDfromCylindrical(double r,double phi,double z)
        {
           double theta = Math.atan2(r,z);
           return constructID(r,z,theta,phi);
        }
        public int findTowerIDfromSpherical(double rr,double theta,double phi)
        {
           double z = rr*Math.cos(theta);
           double r = rr*Math.sin(theta);
           return constructID(r,z,theta,phi);
        }
        public int constructID(double r, double z, double theta, double phi)
        {
           int invalidID = 0;
           int validID = 0;
           int system = -1;
           int ec;
           int layer;
           int phibin;
           int thetabin;
           Calorimeter c = null;
           c = findSystem(r,z);
           if (c == null)return invalidID;
		   system = foundSystem;
           if(system < 0)return invalidID;
           ec = 0;
           if(foundEndcap == 1)ec = 1;
           phibin = (int) (phi*c.getPhiBins()/2./Math.PI);
           thetabin = (int) (theta*c.getThetaBins()/Math.PI);
		   if(ec == 1)
		   {
             layer = (int) ((z - c.getZInner())*c.getLayers()/
                     (c.getZOuter() - c.getZInner()));
             if(layer > c.getLayers())layer = c.getLayers() - 1;
             if(layer < 0)layer = 0;
		   }
		   else
		   {
             layer = (int) ((r - c.getInnerRadius())*c.getLayers()/
                     (c.getOuterRadius() - c.getInnerRadius()));
             if(layer > c.getLayers())layer = c.getLayers() - 1;
             if(layer < 0)layer = 0;
		   } 
           validID = (phibin << PHI_OFFSET) & PHI_MASK;
           validID = validID | ((thetabin << THETA_OFFSET) & THETA_MASK);
           validID = validID | ((layer << LAYER_OFFSET) & LAYER_MASK);
           validID = validID | ((ec << BARRELENDCAP_OFFSET) & BARRELENDCAP_MASK);
           validID = validID | ((system << SYSTEM_OFFSET) & SYSTEM_MASK);
           return validID;
        }
 
 		/*
		* Return TowerID given theta, phi, layer indices and calorimeter System
		* Allowed values of System are:
		* 
		* EM, HAD, MU, LUM 
		*
		* + _Barrel, _Endcap_North, _Endcap_South
		*/
        public int constructID(int thetabin, int phibin, int layer, String system)
        {
		   int validID=0;
		   int systemID =0;
		   // This is messy...
		   // Find the system, e.g. EM, HAD, MU, LUM 
		   for(int i=0; i<systemNames.length; ++i)
		   {
		   	if(system.indexOf(systemNames[i])!=-1) systemID=i;
		   }
		   if(systemID==0) return 0;
		   int ec=0;
		   // Is this system endcap?
		   if(system.indexOf("_EndCap_")!=-1) ec=1;
		   
           validID = (phibin << PHI_OFFSET) & PHI_MASK;
           validID = validID | ((thetabin << THETA_OFFSET) & THETA_MASK);
           validID = validID | ((layer << LAYER_OFFSET) & LAYER_MASK);
           validID = validID | ((ec << BARRELENDCAP_OFFSET) & BARRELENDCAP_MASK);
           validID = validID | ((systemID << SYSTEM_OFFSET) & SYSTEM_MASK);
           return validID;		
        }
		
        public Calorimeter findSystem(double r,double z)
        {
           Calorimeter sys = null;
           foundEndcap = 0;
           for (int i=0;i<8;i++)
           {
	       String name = systemNames[i];
           name +=  "_Barrel";
	       Object o =  det.getComponent(name);
	       if (o instanceof Calorimeter) 
	       {
		      sys = (Calorimeter) o;
              if( (r > sys.getOuterRadius()) | (r < sys.getInnerRadius()) |
                     (z > sys.getZOuter()) | (z < sys.getZInner()))
              {
                 sys = null;
              }
              else 
              {
                 foundEndcap = 0;
				 foundSystem = i;
                 return sys;
              }
           }
	       name = systemNames[i];
           name +=  "_EndCap_North";
	       o =  det.getComponent(name);
	       if (o instanceof Calorimeter) 
	       {
		      sys = (Calorimeter) o;
              if( (r > sys.getOuterRadius()) | (r < sys.getInnerRadius()) |
                     (z > sys.getZOuter()) | (z < sys.getZInner()))
              {
                 sys = null;
              }
              else 
              {
                 foundEndcap = 1;
				 foundSystem = i;
                 return sys;
              }
           }
	       name = systemNames[i];
           name +=  "_EndCap_South";
	       o =  det.getComponent(name);
	       if (o instanceof Calorimeter) 
	       {
		      sys = (Calorimeter) o;
              if( (r > sys.getOuterRadius()) | (r < sys.getInnerRadius()) |
                     (z < sys.getZOuter()) | (z > sys.getZInner()))
              {
                 sys = null;
              }
              else 
              {
                 foundEndcap = 1;
				 foundSystem = i;
                 return sys;
              }
           }
        } 
        return sys;  
     }

	/**
	 * Returns an array of towerID's for the neighbouring cells
	 */
	public int[] getNeighbouringCells()
	{
		int[] result = new int[26]; // 26 is max possible number of neighbours
		int n = 0; // neighbors currently found
		
		if (comp == null) comp = findComponent();		
		int layer = getLayer();
		int maxLayers = comp.getLayers();
		int phiBin = getPhiBin();
		int maxPhi = comp.getPhiBins();
		int thetaBin = getThetaBin();
		int maxTheta = comp.getThetaBins();
		int baseid = id & ~(PHI_MASK | THETA_MASK | LAYER_MASK);
		
		for (int ll = Math.max(0,layer-1); ll<Math.min(maxLayers,layer+2); ll++)
		{
			for (int p = phiBin - 1; p<phiBin+2; p++)
			{
				int pp = p % maxPhi;
				for (int tt = Math.max(0,thetaBin-1); tt<Math.min(maxTheta,thetaBin+2); tt++)
				{
					int newid = baseid | ll<<LAYER_OFFSET | pp<<PHI_OFFSET | tt<<THETA_OFFSET;;
					if (newid == id) continue; // We are not our own neighbour
					result[n++] = newid;
				} 
			}	
		}
		
		if (n == result.length) return result;
		// truncate the array to the length actually used
		int[] trunc = new int[n];
		System.arraycopy(result,0,trunc,0,n);
		return trunc;
	}
	protected SegmentedDetectorComponent findComponent()
	{
		int system = getSystem();
		boolean endcap = isEndcap();
		boolean north = isNorth();
		if (system == cacheSystem && endcap == cacheEndcap && north == cacheNorth)
		{
			return cacheComponent;
		}
		// hack: This is pretty ugly code!
		// Extra ugly -- we have to assume we are in the south end cap, since 
		// we cannot find out which one we are really in until we have the 
		// number of theta bins, which we do not get until we have the component
		
		String systemName = systemNames[system];
		systemName += endcap ? "_EndCap_South"  : "_Barrel";
		Object o =  det.getComponent(systemName);
		if (o instanceof SegmentedDetectorComponent) 
		{
			cacheComponent = (SegmentedDetectorComponent) o;
			cacheTheta = cacheComponent.getThetaBins();
			north = isNorth();
			if (endcap && north)
			{
				systemName = systemNames[system] + "_EndCap_North";
				o =  det.getComponent(systemName);
				cacheComponent = (SegmentedDetectorComponent) o;
			}  
			cacheSystem = system;
			cacheEndcap = endcap;
			cacheNorth = north;
			return cacheComponent;
		}
		throw new hep.lcd.util.error.LCDException("Cannot find component "+systemName+" towerid="+Integer.toHexString(id));
	}
	final public int getThetaBin()
	{
		return (id & THETA_MASK) >> THETA_OFFSET;
	}
	final public int getPhiBin()
	{
		return (id & PHI_MASK) >> PHI_OFFSET;		
	}
	final public int getLayer()
	{
		return (id & LAYER_MASK) >> LAYER_OFFSET;		
	}
	final public int getSystem()
	{
		return (id & SYSTEM_MASK) >>> SYSTEM_OFFSET;		
	}
	final public boolean isEndcap()
	{
		return (id & BARRELENDCAP_MASK) != 0;
	}
	final public boolean isNorth()
	{
		return (getThetaBin() < cacheTheta/2) ;
	}
	private static final int THETA_MASK        = 0x001FF800;
	private static final int PHI_MASK          = 0x000007FF;
	private static final int LAYER_MASK        = 0x0FE00000;
	private static final int SYSTEM_MASK       = 0xE0000000;
	private static final int BARRELENDCAP_MASK = 0x10000000;
	
	private static final int THETA_OFFSET        = findFirstBit(THETA_MASK);
	private static final int PHI_OFFSET          = findFirstBit(PHI_MASK);
	private static final int LAYER_OFFSET        = findFirstBit(LAYER_MASK);
	private static final int SYSTEM_OFFSET       = findFirstBit(SYSTEM_MASK);
	private static final int BARRELENDCAP_OFFSET = findFirstBit(BARRELENDCAP_MASK);
	
	private static final int SYSTEM_EM = 5;
	private static final int SYSTEM_HAD = 6;
	private static final int SYSTEM_MU = 7;
	private static final int SYSTEM_LUM = 2;
	
	private int cacheSystem = -1;
	private boolean cacheEndcap;
	private boolean cacheNorth;
	private int cacheTheta;
	private SegmentedDetectorComponent cacheComponent;
    private int foundEndcap;
    private int foundSystem;
	
	private static String[] systemNames = { "??", "??", "LUM", "??", "??", "EM", "HAD", "MU" };
}

