// --------------------------------------------------------------------
// wm3d - A Flash Molecular Viewer
//
// Copyright (c) 2011-2013, tamanegi (tamanegi@users.sourceforge.jp)
// All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// --------------------------------------------------------------------

package pdb;

class MyLine {
  @:isVar public var gradient( get, set ):Float;
    public function get_gradient():Float { return( gradient ); }
    public function set_gradient( g:Float ):Float {
      gradient = g;
      return( gradient );
    }
  @:isVar public var intercept( get, set ):Float;
    public function get_intercept():Float { return( intercept ); }
    public function set_intercept( i:Float ):Float {
      intercept = i;
      return( intercept );
    }
  // ---------------------------------------------------------------------
  public function new( ?gr:Float,
                       ?ic:Float ) {
    gradient = gr;
    intercept = ic;
  }

  public function getValue( x:Float ):Float {
    return( gradient * x + intercept );
  }

  public function crossPointMyLine( l:MyLine ):Dynamic {
    return( crossPoint( l.gradient, l.intercept ) );
  }

  public function crossPoint( gr:Float,
                              ic:Float ):Dynamic {
    var del_grad:Float = gradient - gr;
    var del_inter:Float = ic - intercept;
    if ( del_grad == 0.0 ) return null;
    var crossx = del_inter / del_grad;
    var crossy = getValue( crossx );
    var anon = { x:crossx, y:crossy };
    return( anon );
  }

  // generate MyLine which passes through (px,py) and vertical to me
  public function genLineFromPoint( px:Float,
                                    py:Float ):MyLine {
    var gr = -1.0 / gradient;
    var ic = py - gr * px;
    return( new MyLine( gr, ic ) );
  }
}

class RamaParam {
  public var line( default, null ):MyLine;
  public var alpha( default, null ):Float;
  public var minp( default, null ):Float;
  public var maxp( default, null ):Float;
  // ----------------------------------------------------------------------
  public function new( ?gr:Float = 1.0,
                       ?ic:Float = 0.0,
                       ?a:Float = 0.0,
                       ?mp:Float = 0.0,
                       ?pp:Float = 0.0 ) {
    line = new MyLine( gr, ic );
    alpha = 1.0 / ( a * a );
    minp = mp;
    maxp = pp;
  }

  public function calcValue( ras:RamaAngles ):Float {
    if ( !ras.getPhi().avail || !ras.getPsi().avail ) return( 0.0 );
    var phi = 180 * ras.getPhi().val / Math.PI;
    var psi = 180 * ras.getPsi().val / Math.PI;
    var mp_phi = ( minp + maxp ) / 2.0;
    var mp_psi = line.getValue( mp_phi );
    phi = phi - mp_phi;
    phi = mp_phi + __unrollAngleDegree( phi );
    psi = psi - mp_psi;
    psi = mp_psi + __unrollAngleDegree( psi );
    var myl = line.genLineFromPoint( phi, psi );
    var cp = line.crossPointMyLine( myl );
    var delphi = __unrollAngleDegree( phi - cp.x );
    var delpsi = __unrollAngleDegree( psi - cp.y );
    var distsq = delphi * delphi + delpsi * delpsi;
    if ( __unrollAngleDegree( cp.x - minp ) < 0 ) {
      delphi = __unrollAngleDegree( cp.x - minp );
      delpsi = __unrollAngleDegree( cp.y - line.getValue( minp ) );
      distsq += delphi * delphi + delpsi * delpsi;
    } else if ( __unrollAngleDegree( cp.x - maxp ) > 0 ) {
      delphi = __unrollAngleDegree( cp.x - maxp );
      delpsi = __unrollAngleDegree( cp.y - line.getValue( maxp ) );
      distsq += delphi * delphi + delpsi * delpsi;
    }
    return( Math.exp( -alpha * distsq ) );
  }

  private function __unrollAngleDegree( a:Float ):Float {
    return( a - 360 * Math.fround( a / 360 ) );
  }
}

/**
  Pramaeters related with Ramachandran plot
**/
class RamaParams {
  /**
    threshold of score for alpha helix
  **/
  static public var threshold_hA( default, null ):Float = 0.7 * 5.0;
  /**
    threshold of score for 3-10 helix
  **/
  static public var threshold_h3( default, null ):Float = 0.7 * 4.0;
  /**
    threshold of score for pi helix
  **/
  static public var threshold_hP( default, null ):Float = 0.7 * 6.0;
  /**
    threshold of score for beta strand
  **/
  static public var threshold_s( default, null ):Float = 0.55 * 2.0;

  static public var helixA( default, null ):RamaParam = new RamaParam( -1.0, -105.0, 45.0, -90.0, -35.0 );
  static public var helix310( default, null ):RamaParam = new RamaParam( -1.0, -75.0, 50.0, -90.0, -15.0 );
  static public var helixPI( default, null ):RamaParam = new RamaParam( -1.0, -125.0, 40.0, -85.0, -40.0 );
  static public var strand0( default, null ):RamaParam = new RamaParam( -1.0, 0.0, 40.0, -160.0, -100.0 );
  static public var strand1( default, null ):RamaParam = new RamaParam( -2.0, 0.0, 40.0, -80.0, -40.0 );
  // -------------------------------------------------------------------------
  static public function getValueHelixAlpha( ras:RamaAngles ):Float {
    return( RamaParams.helixA.calcValue( ras ) );
  }
  static public function getValueHelix310( ras:RamaAngles ):Float {
    return( RamaParams.helix310.calcValue( ras ) );
  }
  static public function getValueHelixPI( ras:RamaAngles ):Float {
    return( RamaParams.helixPI.calcValue( ras ) );
  }
  static public function getValueStrand( ras:RamaAngles ):Float {
    return( Math.max( RamaParams.strand0.calcValue( ras ),
                      RamaParams.strand1.calcValue( ras ) ) );
  }
}
