// get neigbouring particles of P and calculate the according connection probabilities
void MetropolisHastingsSampler::ComputeEndPointProposalDistribution(EndPoint P)
{
    Particle *p = P.p;
    int ep = P.ep;

    float dist,dot;
    vnl_vector_fixed<float, 3> R = p->GetPos() + (p->GetDir() * (ep*m_ParticleLength) );
    m_ParticleGrid->ComputeNeighbors(R);
    m_SimpSamp.clear();

    m_SimpSamp.add(m_StopProb,EndPoint(nullptr,0));

    for (;;)
    {
        Particle *p2 =  m_ParticleGrid->GetNextNeighbor();
        if (p2 == nullptr) break;
        if (p!=p2 && p2->label == 0)
        {
            if (p2->mID == -1)
            {
                dist = (p2->GetPos() - p2->GetDir() * m_ParticleLength - R).squared_magnitude();
                if (dist < m_DistanceThreshold)
                {
                    dot = dot_product(p2->GetDir(),p->GetDir()) * ep;
                    if (dot > m_CurvatureThreshold)
                    {
                        float en = m_EnergyComputer->ComputeInternalEnergyConnection(p,ep,p2,-1);
                        m_SimpSamp.add(exp(en/m_TractProb),EndPoint(p2,-1));
                    }
                }
            }
            if (p2->pID == -1)
            {
                dist = (p2->GetPos() + p2->GetDir() * m_ParticleLength - R).squared_magnitude();
                if (dist < m_DistanceThreshold)
                {
                    dot = dot_product(p2->GetDir(),p->GetDir()) * (-ep);
                    if (dot > m_CurvatureThreshold)
                    {
                        float en = m_EnergyComputer->ComputeInternalEnergyConnection(p,ep,p2,+1);
                        m_SimpSamp.add(exp(en/m_TractProb),EndPoint(p2,+1));
                    }
                }
            }
        }
    }
}
// generate actual proposal (birth, death, shift and connection of particle)
void MetropolisHastingsSampler::MakeProposal()
{
    float randnum = m_RandGen->GetVariate();

    // Birth Proposal
    if (randnum < m_BirthProb)
    {
        m_BirthTime.Start();
        vnl_vector_fixed<float, 3> R;
        m_EnergyComputer->DrawRandomPosition(R);
        vnl_vector_fixed<float, 3> N = GetRandomDirection();
        Particle prop;
        prop.GetPos() = R;
        prop.GetDir() = N;

        float prob =  m_Density * m_DeathProb /((m_BirthProb)*(m_ParticleGrid->m_NumParticles+1));

        float ex_energy = m_EnergyComputer->ComputeExternalEnergy(R,N,nullptr);
        float in_energy = m_EnergyComputer->ComputeInternalEnergy(&prop);
        prob *= exp((in_energy/m_InTemp+ex_energy/m_ExTemp)) ;

        if (prob > 1 || m_RandGen->GetVariate() < prob)
        {
            Particle *p = m_ParticleGrid->NewParticle(R);
            if (p!=nullptr)
            {
                p->GetPos() = R;
                p->GetDir() = N;
                m_AcceptedProposals++;
            }
        }
        m_BirthTime.Stop();
    }
    // Death Proposal
    else if (randnum < m_BirthProb+m_DeathProb)
    {
        m_DeathTime.Start();
        if (m_ParticleGrid->m_NumParticles > 0)
        {
            int pnum = m_RandGen->GetIntegerVariate()%m_ParticleGrid->m_NumParticles;
            Particle *dp = m_ParticleGrid->GetParticle(pnum);
            if (dp->pID == -1 && dp->mID == -1)
            {
                float ex_energy = m_EnergyComputer->ComputeExternalEnergy(dp->GetPos(),dp->GetDir(),dp);
                float in_energy = m_EnergyComputer->ComputeInternalEnergy(dp);

                float prob = m_ParticleGrid->m_NumParticles * (m_BirthProb) /(m_Density*m_DeathProb); //*SpatProb(dp->R);
                prob *= exp(-(in_energy/m_InTemp+ex_energy/m_ExTemp)) ;
                if (prob > 1 || m_RandGen->GetVariate() < prob)
                {
                    m_ParticleGrid->RemoveParticle(pnum);
                    m_AcceptedProposals++;
                }
            }
        }
        m_DeathTime.Stop();
    }
    // Shift Proposal
    else  if (randnum < m_BirthProb+m_DeathProb+m_ShiftProb)
    {
        if (m_ParticleGrid->m_NumParticles > 0)
        {
            m_ShiftTime.Start();
            int pnum = m_RandGen->GetIntegerVariate()%m_ParticleGrid->m_NumParticles;
            Particle *p =  m_ParticleGrid->GetParticle(pnum);
            Particle prop_p = *p;

            DistortVector(m_Sigma, prop_p.GetPos());
            DistortVector(m_Sigma/(2*m_ParticleLength), prop_p.GetDir());
            prop_p.GetDir().normalize();


            float ex_energy = m_EnergyComputer->ComputeExternalEnergy(prop_p.GetPos(),prop_p.GetDir(),p)
                    - m_EnergyComputer->ComputeExternalEnergy(p->GetPos(),p->GetDir(),p);
            float in_energy = m_EnergyComputer->ComputeInternalEnergy(&prop_p) - m_EnergyComputer->ComputeInternalEnergy(p);

            float prob = exp(ex_energy/m_ExTemp+in_energy/m_InTemp);
            if (m_RandGen->GetVariate() < prob)
            {
                vnl_vector_fixed<float, 3> Rtmp = p->GetPos();
                vnl_vector_fixed<float, 3> Ntmp = p->GetDir();
                p->GetPos() = prop_p.GetPos();
                p->GetDir() = prop_p.GetDir();
                if (!m_ParticleGrid->TryUpdateGrid(pnum))
                {
                    p->GetPos() = Rtmp;
                    p->GetDir() = Ntmp;
                }
                m_AcceptedProposals++;
            }
            m_ShiftTime.Stop();
        }
    }
    // Optimal Shift Proposal
    else  if (randnum < m_BirthProb+m_DeathProb+m_ShiftProb+m_OptShiftProb)
    {
        if (m_ParticleGrid->m_NumParticles > 0)
        {
            m_OptShiftTime.Start();
            int pnum = m_RandGen->GetIntegerVariate()%m_ParticleGrid->m_NumParticles;
            Particle *p =  m_ParticleGrid->GetParticle(pnum);

            bool no_proposal = false;
            Particle prop_p = *p;
            if (p->pID != -1 && p->mID != -1)
            {
                Particle *plus = m_ParticleGrid->GetParticle(p->pID);
                int ep_plus = (plus->pID == p->ID)? 1 : -1;
                Particle *minus = m_ParticleGrid->GetParticle(p->mID);
                int ep_minus = (minus->pID == p->ID)? 1 : -1;
                prop_p.GetPos() = (plus->GetPos() + plus->GetDir() * (m_ParticleLength * ep_plus)  + minus->GetPos() + minus->GetDir() * (m_ParticleLength * ep_minus));
                prop_p.GetPos() *= 0.5;
                prop_p.GetDir() = plus->GetPos() - minus->GetPos();
                prop_p.GetDir().normalize();
            }
            else if (p->pID != -1)
            {
                Particle *plus = m_ParticleGrid->GetParticle(p->pID);
                int ep_plus = (plus->pID == p->ID)? 1 : -1;
                prop_p.GetPos() = plus->GetPos() + plus->GetDir() * (m_ParticleLength * ep_plus * 2);
                prop_p.GetDir() = plus->GetDir();
            }
            else if (p->mID != -1)
            {
                Particle *minus = m_ParticleGrid->GetParticle(p->mID);
                int ep_minus = (minus->pID == p->ID)? 1 : -1;
                prop_p.GetPos() = minus->GetPos() + minus->GetDir() * (m_ParticleLength * ep_minus * 2);
                prop_p.GetDir() = minus->GetDir();
            }
            else
                no_proposal = true;

            if (!no_proposal)
            {
                float cos = dot_product(prop_p.GetDir(), p->GetDir());
                float p_rev = exp(-((prop_p.GetPos()-p->GetPos()).squared_magnitude() + (1-cos*cos))*m_Gamma)/m_Z;

                float ex_energy = m_EnergyComputer->ComputeExternalEnergy(prop_p.GetPos(),prop_p.GetDir(),p)
                        - m_EnergyComputer->ComputeExternalEnergy(p->GetPos(),p->GetDir(),p);
                float in_energy = m_EnergyComputer->ComputeInternalEnergy(&prop_p) - m_EnergyComputer->ComputeInternalEnergy(p);

                float prob = exp(ex_energy/m_ExTemp+in_energy/m_InTemp)*m_ShiftProb*p_rev/(m_OptShiftProb+m_ShiftProb*p_rev);

                if (m_RandGen->GetVariate() < prob)
                {
                    vnl_vector_fixed<float, 3> Rtmp = p->GetPos();
                    vnl_vector_fixed<float, 3> Ntmp = p->GetDir();
                    p->GetPos() = prop_p.GetPos();
                    p->GetDir() = prop_p.GetDir();
                    if (!m_ParticleGrid->TryUpdateGrid(pnum))
                    {
                        p->GetPos() = Rtmp;
                        p->GetDir() = Ntmp;
                    }
                    m_AcceptedProposals++;
                }
            }
            m_OptShiftTime.Stop();
        }
    }
    // Connection Proposal
    else
    {
        if (m_ParticleGrid->m_NumParticles > 0)
        {
            m_ConnectionTime.Start();
            int pnum = m_RandGen->GetIntegerVariate()%m_ParticleGrid->m_NumParticles;
            Particle *p = m_ParticleGrid->GetParticle(pnum);

            EndPoint P;
            P.p = p;
            P.ep = (m_RandGen->GetVariate() > 0.5)? 1 : -1; // direction of the new tract

            RemoveAndSaveTrack(P);  // remove old tract and save it for later
            if (m_BackupTrack.m_Probability != 0)
            {
                MakeTrackProposal(P);   // propose new tract starting from P

                float prob = (m_ProposalTrack.m_Energy-m_BackupTrack.m_Energy)/m_InTemp ;

                prob = exp(prob)*(m_BackupTrack.m_Probability * pow(m_DelProb,m_ProposalTrack.m_Length))
                        /(m_ProposalTrack.m_Probability * pow(m_DelProb,m_BackupTrack.m_Length));
                if (m_RandGen->GetVariate() < prob)
                {
                    ImplementTrack(m_ProposalTrack);    // accept proposed tract
                    m_AcceptedProposals++;
                }
                else
                {
                    ImplementTrack(m_BackupTrack);  // reject proposed tract and restore old one
                }
            }
            else
                ImplementTrack(m_BackupTrack);
            m_ConnectionTime.Stop();
        }
    }
}