#include "Geometry/CSCGeometry/interface/CSCWireTopology.h"

#include "Geometry/CSCGeometry/src/CSCGangedWireGrouping.h"
#include "Geometry/CSCGeometry/src/CSCNonslantedWireGeometry.h"
#include "Geometry/CSCGeometry/src/CSCSlantedWireGeometry.h"

#include <FWCore/MessageLogger/interface/MessageLogger.h>

#include "DataFormats/Math/interface/GeantUnits.h"

#include <cmath>

using namespace geant_units;
using namespace geant_units::operators;

CSCWireTopology::~CSCWireTopology() {
  delete theWireGrouping;
  delete theWireGeometry;
}

CSCWireTopology::CSCWireTopology(const CSCWireGroupPackage& wg, double yOfFirstWire, float wireAngleInDegrees)
    : theWireGrouping(nullptr), theWireGeometry(nullptr), theAlignmentPinToFirstWire(wg.alignmentPinToFirstWire / 10.) {
  // Pass consecutiveGroups and wiresInConsecutiveGroups
  // directly on to theWireGrouping ctor. These vectors
  // are transient info and can go once ctor is complete.

  theWireGrouping = new CSCGangedWireGrouping(wg.consecutiveGroups, wg.wiresInEachGroup, wg.numberOfGroups);

  const float zeroprecision = 1.E-06;  // blur zero a bit, for comparisons

  float wireAngleInRadians = convertDegToRad(wireAngleInDegrees);

  //@@ Conversion from mm to cm
  float wireSpacing = convertMmToCm(wg.wireSpacing);
  float nw = convertMmToCm(wg.narrowWidthOfWirePlane);
  float ww = convertMmToCm(wg.wideWidthOfWirePlane);
  float lw = convertMmToCm(wg.lengthOfWirePlane);

  LogTrace("CSCWireTopology|CSC") << "CSCWireTopology constructing CSCWireGeometry with:\n"
                                  << " wireSpacing = " << wireSpacing * 10. << " (mm) "
                                  << ", yOfFirstWire = " << yOfFirstWire << " (cm) "
                                  << ", wireAngle = " << wireAngleInDegrees << " (deg) = " << wireAngleInRadians
                                  << " (rads)"
                                  << ", extent: n, w, l = " << nw << ", " << ww << ", " << lw << " (cm)";

  if (fabs(wireAngleInDegrees) > zeroprecision) {
    theWireGeometry = new CSCSlantedWireGeometry(wireSpacing, yOfFirstWire, nw, ww, lw, wireAngleInRadians);
  } else {
    theWireGeometry = new CSCNonslantedWireGeometry(wireSpacing, yOfFirstWire, nw, ww, lw);
  }
}

CSCWireTopology::CSCWireTopology(const CSCWireTopology& mewt)
    : theAlignmentPinToFirstWire(mewt.theAlignmentPinToFirstWire) {
  if (mewt.theWireGrouping)
    theWireGrouping = mewt.theWireGrouping->clone();
  else
    theWireGrouping = nullptr;
  if (mewt.theWireGeometry)
    theWireGeometry = mewt.theWireGeometry->clone();
  else
    theWireGeometry = nullptr;
}

CSCWireTopology& CSCWireTopology::operator=(const CSCWireTopology& mewt) {
  if (&mewt != this) {
    delete theWireGrouping;
    if (mewt.theWireGrouping)
      theWireGrouping = mewt.theWireGrouping->clone();
    else
      theWireGrouping = nullptr;

    delete theWireGeometry;
    if (mewt.theWireGeometry)
      theWireGeometry = mewt.theWireGeometry->clone();
    else
      theWireGeometry = nullptr;

    theAlignmentPinToFirstWire = mewt.theAlignmentPinToFirstWire;
  }
  return *this;
}

LocalPoint CSCWireTopology::localPosition(const MeasurementPoint&) const {
  edm::LogWarning("CSC") << "CSCWireTopology: localPosition unimplemented. Don't use it."
                         << "\n";
  return LocalPoint();
}

LocalError CSCWireTopology::localError(const MeasurementPoint&, const MeasurementError&) const {
  edm::LogWarning("CSC") << "CSCWireTopology: localError unimplemented. Don't use it."
                         << "\n";
  return LocalError();
}

MeasurementPoint CSCWireTopology::measurementPosition(const LocalPoint&) const {
  edm::LogWarning("CSC") << "CSCWireTopology: measurementPosition unimplemented. Don't use it."
                         << "\n";
  return MeasurementPoint();
}

MeasurementError CSCWireTopology::measurementError(const LocalPoint&, const LocalError&) const {
  edm::LogWarning("CSC") << "CSCWireTopology: measurementError unimplemented. Don't use it."
                         << "\n";
  return MeasurementError();
}

int CSCWireTopology::channel(const LocalPoint& p) const {
  int wire = theWireGeometry->nearestWire(p);
  int group = theWireGrouping->wireGroup(wire);
  return group;  // 0 means out of range or dead region
}

float CSCWireTopology::yOfWireGroup(int wireGroup, float x) const {
  float wire = middleWireOfGroup(wireGroup);
  return theWireGeometry->yOfWire(wire, x);
}

float CSCWireTopology::yResolution(int wireGroup) const {
  // Return the resolution in the wire group measurement (in cm).
  // To really be resolution in local y this should be scaled by Sin
  // factors involving strip and wire angles, and also add a contribution
  // from strip measurement (which is negligible w.r.t. wire resolution.)
  // These factors can increase the number here by up to 1.26.

  // Should be improvable for smaller partial wire groups

  return wireSpacing() * theWireGrouping->numberOfWiresPerGroup(wireGroup) / sqrt(12.);
}

std::pair<float, float> CSCWireTopology::equationOfWire(float wire) const {
  return theWireGeometry->equationOfWire(wire);
}

float CSCWireTopology::restrictToYOfWirePlane(float y) const {
  // Reset y to lie within bounds of wire plane at top and bottom.

  std::pair<float, float> ylim = theWireGeometry->yLimitsOfWirePlane();

  if (y < ylim.first) {
    y = ylim.first;
  } else if (y > ylim.second) {
    y = ylim.second;
  }
  return y;
}

bool CSCWireTopology::insideYOfWirePlane(float y) const {
  // Returns true if arg falls within y limits of wire plane; false otherwise.

  std::pair<float, float> ylim = theWireGeometry->yLimitsOfWirePlane();

  if (y < ylim.first) {
    return false;
  } else if (y > ylim.second) {
    return false;
  }
  return true;
}
