<!DOCTYPE html>
<html ng-app="app">

  <head>
    <link data-require="font-awesome@*" data-semver="4.5.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.css" />
    <link data-require="bootstrap-css@3.3.6" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link data-require="animate.css@*" data-semver="3.2.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/animate.css/3.2.0/animate.min.css" />
    <link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.css" />
    <link data-require="ngToast@*" data-semver="1.5.0" rel="stylesheet" href="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast-animations.css" />
    <script data-require="jquery@*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script data-require="angular.js@1.4.9" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.js"></script>
    <script data-require="ui-router@*" data-semver="0.2.18" src="//cdn.rawgit.com/angular-ui/ui-router/0.2.18/release/angular-ui-router.js"></script>
    <script data-require="underscore.js@*" data-semver="1.8.3" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
    <script data-require="ui-bootstrap@*" data-semver="1.3.2" src="https://cdn.rawgit.com/angular-ui/bootstrap/gh-pages/ui-bootstrap-tpls-1.3.2.js"></script>
    <script data-require="angular-sanitize@*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular-sanitize.js"></script>
    <script data-require="angular-animate@*" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular-animate.js"></script>
    <script data-require="ngToast@*" data-semver="1.5.0" src="//rawgit.com/tameraydin/ngToast/1.5.0/dist/ngToast.js"></script>
    <script src="angular-clipboard.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="app.js"></script>
    <script src="KinobodyProgramDataService.js"></script>
    <script src="MainController.js"></script>
  </head>

  <body ng-controller="mainController as vm">
    <toast></toast>
    <div ui-view=""></div>
    <div class="container-fluid">
      <form>
        <div class="row">
          <div class="col-md-12">
            <h4>Kinobody Calories / Macros</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Kinobody Program</span>
                <select class="form-control" ng-options="(option.name + ' - ' + option.description) for option in vm.programOptionSet" ng-model="vm.selectedProgram" title="{{vm.selectedProgram.description}}" ng-change="vm.onProgramOptionChange()"></select>
              </div>
            </div>
          </div>
        </div>
        <div class="row" ng-show="vm.activeProgramSubOptionSet.length > 1">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Program Options</span>
                <select class="form-control" ng-options="(option.name) for option in vm.activeProgramSubOptionSet" ng-model="vm.selectedProgramSubOption" ng-change="vm.onProgramSubOptionChange()"></select>
              </div>
            </div>
          </div>
        </div>
        <div class="row" ng-show="vm.activeMacroOptionSet.length > 1">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group input-select">
                <span class="input-group-addon input-label">Macro Options</span>
                <select class="form-control" ng-options="(option.name) for option in vm.activeMacroOptionSet" ng-model="vm.selectedMacroOption" ng-change="vm.onMacroOptionChange()"></select>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group">
                <span class="input-group-addon input-label">{{vm.inputWeightLabel}}</span>
                <input type="text" class="form-control" ng-model="vm.inputWeight" ng-model-options="{debounce: 500}" ng-change="vm.onWeightChange()" />
                <span class="input-group-btn">
                  <button type="button" class="btn btn-toggle" ng-click="vm.toggleInputUnitsWeight()" tabindex="-1" title="Click to toggle kg/lbs">
                    {{vm.inputUnitsWeight}}&nbsp;<i class="fa fa-toggle-on" ng-class="{'fa-flip-horizontal': vm.inputUnitsWeight !== 'kg'}"></i>
                  </button>
                </span>
              </div>
            </div>
            <div ng-show="vm.hasGoalNotes">
              <ul>
                <li ng-repeat="note in vm.goalNotes">
                  {{note.text}}
                </li>
              </ul>
            </div>
          </div>
        </div>
        <div class="row" ng-show="vm.kcalOffsetActive">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group has-warning">
              <div class="input-group">
                <span class="input-group-addon input-label">kcal Offset</span>
                <input type="text" class="form-control" ng-model="vm.kcalOffset" ng-model-options="{debounce: 500}" ng-change="vm.onKcalOffsetChange()" />
                <span class="input-group-addon">kcal</span>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <fieldset disabled="">
              <div class="form-group">
                <div class="input-group">
                  <span class="input-group-addon input-label">Maintenance (lbs x 15)</span>
                  <input type="text" ng-model="vm.kinobodyEstimateMaintenanceCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
            </fieldset>
          </div>
        </div>
        <hr />
        
        <div class="row">
          <div class="col-md-12">
            <h4>{{vm.macroSectionLabel}}</h4>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <fieldset disabled="">
              <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
                <div class="input-group">
                  <span class="input-group-addon input-label">{{vm.dailyCaloriesLabel}}</span>
                  <input type="text" ng-model="vm.kinobodyEstimateDailyCalories_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                  <span class="input-group-addon">kcal</span>
                </div>
              </div>
            </fieldset>
          </div>
        </div>
        <fieldset disabled="">
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Protein</span>
                      <input type="text" ng-model="vm.macrosProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageProtein" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Fats</span>
                      <input type="text" ng-model="vm.macrosFats" id="macrosFat" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageFats" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <div class="row">
                <div class="col-xs-8">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <span class="input-group-addon input-macros">Carbs</span>
                      <input type="text" ng-model="vm.macrosCarbs" id="macrosCarb" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">g</span>
                    </div>
                  </div>
                </div>
                <div class="col-xs-4">
                  <div class="form-group has-success">
                    <div class="input-group">
                      <input type="text" ng-model="vm.percentageCarbs" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                      <span class="input-group-addon">%</span>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </fieldset>
        
        <div ng-show="vm.hasTrainingRestDaysSplit">
          <hr />
          <div class="row">
            <div class="col-md-12">
              <h4>{{vm.macroSectionLabel_rest}}</h4>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <fieldset disabled="">
                <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
                  <div class="input-group">
                    <span class="input-group-addon input-label">{{vm.dailyCaloriesLabel_rest}}</span>
                    <input type="text" ng-model="vm.kinobodyEstimateDailyCalories_rest_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                    <span class="input-group-addon">kcal</span>
                  </div>
                </div>
              </fieldset>
            </div>
          </div>
          <fieldset disabled="">
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Protein</span>
                        <input type="text" ng-model="vm.macrosProtein_rest" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageProtein_rest" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Fats</span>
                        <input type="text" ng-model="vm.macrosFats_rest" id="macrosFat" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageFats_rest" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Carbs</span>
                        <input type="text" ng-model="vm.macrosCarbs_rest" id="macrosCarb" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageCarbs_rest" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </fieldset>
        </div>
        
        
        
        <div ng-show="vm.hasRefeedDaySubOption">
          <hr />
          <div class="row">
            <div class="col-md-12">
              <h4>{{vm.macroSectionLabel_refeed}}</h4>
            </div>
          </div>
          <div class="row">
            <div class="col-md-6 col-sm-9 col-xs-12">
              <fieldset disabled="">
                <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
                  <div class="input-group">
                    <span class="input-group-addon input-label">{{vm.dailyCaloriesLabel_refeed}}</span>
                    <input type="text" ng-model="vm.kinobodyEstimateDailyCalories_refeed_display" class="form-control text-right" placeholder="Required: weight" readonly="" />
                    <span class="input-group-addon">kcal</span>
                  </div>
                </div>
              </fieldset>
              <div ng-show="vm.hasRefeedDayNotes">
                <ul>
                  <li ng-repeat="note in vm.refeedDayNotes">
                    {{note.text}}
                  </li>
                </ul>
              </div>
            </div>
          </div>
          <fieldset disabled="">
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Protein</span>
                        <input type="text" ng-model="vm.macrosProtein_refeed" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageProtein_refeed" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Fats</span>
                        <input type="text" ng-model="vm.macrosFats_refeed" id="macrosFat" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageFats_refeed" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div class="row">
              <div class="col-md-6 col-sm-9 col-xs-12">
                <div class="row">
                  <div class="col-xs-8">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <span class="input-group-addon input-macros">Carbs</span>
                        <input type="text" ng-model="vm.macrosCarbs_refeed" id="macrosCarb" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">g</span>
                      </div>
                    </div>
                  </div>
                  <div class="col-xs-4">
                    <div class="form-group has-success">
                      <div class="input-group">
                        <input type="text" ng-model="vm.percentageCarbs_refeed" id="macrosProtein" class="form-control text-right" placeholder="" readonly="" />
                        <span class="input-group-addon">%</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </fieldset>
        </div>
        
        <hr />
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group" ng-class="{'has-success' : vm.activeMaintenanceFormula === 'KB'}">
              <div class="input-group">
                <span class="input-group-addon">Link: </span>
                <input type="text" ng-model="vm.shareLink" class="form-control" />
                <span class="input-group-btn">
                  <button clipboard="" supported="vm.clipboardSupported" text="vm.shareLink" on-copied="vm.onClipboardSuccess()" on-error="onClipboardFail(err)" type="button" class="btn btn-toggle" tabindex="-1" title="Click to copy to clipboard">
                    <i class="fa fa-copy"></i>
                  </button>
                </span>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col-md-6 col-sm-9 col-xs-12">
            <div class="form-group">
              <button type="button" class="btn btn-default" ng-click="vm.resetForm()" tabindex="-1">
                <i class="fa fa-fw fa-remove"></i>
                Clear my data
              </button>
            </div>
          </div>
        </div>
        <div ng-show="vm.displayDebuggingArea">
          <div class="row">
            <div class="col-md-12">
              <h4>Debugging: stuff below is not working yet!</h4>
            </div>
          </div>
          <div>
            <ul>
              <li>Check kcal normal/training day:  {{vm.crossCheckCalories}}</li>
              <li ng-show="vm.hasTrainingRestDaysSplit">Check kcal rest day:  {{vm.crossCheckCalories_rest}}</li>
              <li ng-show="vm.hasRefeedDaySubOption">Check kcal refeed day:  {{vm.crossCheckCalories_refeed}}</li>
            </ul>
          </div>
        </div>
          
      </form>
    </div>
  </body>

</html>
/* Styles go here */

body {
  padding: 15px;
}

.input-label {
  width: 150px;
  text-align: left;
}

.input-macros {
  width: 100px;
  text-align: left;
}

.input-select {
  width: 100%;
}

.btn-toggle {
  color: #A66D3B;
  background-color: #FCF8E3;
  border-color: #8A6D3B;
}
(function() {

  var app = angular.module('app', [
    'ui.router', 
    'ui.bootstrap', 
    'angular-clipboard',
    'ngAnimate',
    'ngSanitize',
    'ngToast'
    ]);

  app.run(['$state', function($state) {
    console.log("app run");
  }]);

  app.config(function() {
    console.log("app config");
  });
  
  app.config(['ngToastProvider', function(ngToastProvider) {
    ngToastProvider.configure({
      animation: 'slide' // or 'fade'
    });
  }]);
  
  app.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', uiRouterConfigurator]);

  function uiRouterConfigurator($stateProvider, $urlRouterProvider, $locationProvider) {
  
    $urlRouterProvider.otherwise('/');
    
    // States
    $stateProvider
      .state('reset', {
          url: '/?kcalOffset',
          // params: {
          //   test: null,
          // },
          //url: '/afl/{weight}_{weightUnits}',
          // templateUrl: 'kinobody_afl.html',
          // controller: 'kinobody_afl_controller',
          // controllerAs: 'vm',
      })
      .state('home', {
          url: '/{programName}/{programSubOptionName}/{macrosOptionName}/{weight}_{weightUnits}?kcalOffset',
          // params: {
          //   test: null,
          // },
          //url: '/afl/{weight}_{weightUnits}',
          // templateUrl: 'kinobody_afl.html',
          // controller: 'kinobody_afl_controller',
          // controllerAs: 'vm',
      })
      // .state('status', {
      //     url: '/status/:statusName',
      //     templateUrl: 'hallo.html',
      // })
    ;
  }

})();
(function() {
  'use strict';
  
  angular.module('app')
    .controller('mainController', MainController);
    
  MainController.$inject = ['$scope', '$log', '$state', '$stateParams', '$location', '$timeout', 'ngToast', 'kinobodyProgramData']; 
    
  function MainController($scope, $log, $state, $stateParams, $location, $timeout, ngToast, kinobodyProgramData) {
    
    var _kgToPoundRatio = 2.2046226218;
    var _cmToInRatio = 0.39370;
    var _inputWeight = '';
    var _inputHeight = '';
    var initialized = false;
    var _initTimeout = 3000;
    
    var _DefaultStartupProgramRoutingName = 'kinobody_afl';
    
    // view model
    var vm = this;
    
    vm.title = 'Kinobody Calories/Macros!';
    vm.clipboardSupported = false;
    vm.weightKg = '';
    vm.selectedProgramRoutingName = _DefaultStartupProgramRoutingName;
    vm.displayDebuggingArea = false;
    
    vm.activeProgramSubOptionSet = '';
    vm.activeMacroOptionSet = '';
    
    vm.selectedProgram = '';
    vm.selectedProgramSubOption = '';
    vm.selectedMacroOption = '';
    
    vm.inputUnitsWeight = 'kg';
    vm.macroSectionLabel = 'Daily calories / macros';
    vm.macroSectionLabel_rest = 'Rest days calories / macros';
    vm.macroSectionLabel_refeed = 'Refeed days calories / macros';
    
    vm.kcalOffset = 0;
    vm.kcalOffsetActive = false;
    
    vm.programOptionSet = kinobodyProgramData.getKinobodyPrograms();

    function init() {
      // process state params here
      processStateParams($stateParams);
      
      updateRoute();
      
      $timeout(function() {
        initialized = true;
      }, _initTimeout);
    }
    
    function processStateParams(stateParams) {
      // $log.log('processStateParams $location', $location);
      $log.log('processStateParams stateParams', stateParams);
      // $log.log('processStateParams $state.params', $state.params);
      var programName = '';
      var programSubOptionName = '';
      var macrosOptionName = '';
      var kcalOffset = 0;
      
      if(stateParams) {

        // restore program by id:
        if (stateParams.programName) {
          programName = stateParams.programName;
          vm.selectedProgramRoutingName = stateParams.programName;
        }
        
        if(stateParams.programSubOptionName) {
          programSubOptionName = stateParams.programSubOptionName;
        }
        
        if(stateParams.macrosOptionName) {
          macrosOptionName = stateParams.macrosOptionName;
        }
        
        if(stateParams.kcalOffset) {
          kcalOffset = stateParams.kcalOffset;
          vm.kcalOffset = Number(stateParams.kcalOffset);
          vm.kcalOffsetActive = true;
        }
        
        // weight / weight units
        if (stateParams.weight && stateParams.weightUnits) {
          var weight = stateParams.weight;
          var units = stateParams.weightUnits.toLowerCase();
          
          vm.inputUnitsWeight = (units === 'lbs') ? 'lbs' : 'kg';
          
          if (vm.inputUnitsWeight === 'kg') {
            vm.weightKg = weight;
          } else {
            vm.weightKg = weight / _kgToPoundRatio;
          }
          
          vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
        }
      }
      
      // initialize controls
      var initializeFromRoute = (programName && programSubOptionName && macrosOptionName);
      $log.log('initializeFromRoute', initializeFromRoute, stateParams, programName, programSubOptionName, macrosOptionName);
      
      // INIT: vm.weightKg is always in kg!  
      vm.inputWeight = (vm.inputUnitsWeight === 'kg') ? vm.weightKg : (vm.weightKg * _kgToPoundRatio);
      vm.inputHeight = (vm.inputUnitsHeight === 'cm') ? vm.heightCm : (vm.heightCm * _cmToInRatio);
      
      //var hasRoutingError = true;
      
      vm.selectedProgram = _.find(vm.programOptionSet, function(item) { 
        return item.routing_name.toUpperCase() === vm.selectedProgramRoutingName.toUpperCase(); 
      });
      
      if (!vm.selectedProgram) {
        vm.selectedProgramRoutingName = _DefaultStartupProgramRoutingName;
        vm.selectedProgram = _.find(vm.programOptionSet, function(item) { 
          return item.routing_name.toUpperCase() === vm.selectedProgramRoutingName.toUpperCase(); 
        });
      } else if (vm.selectedProgram && initializeFromRoute) {
        $log.log('-- selectedProgram success!', programName);
        $log.log('vm.selectedProgram && initializeFromRoute', initializeFromRoute, stateParams);
        
        vm.activeProgramSubOptionSet = vm.selectedProgram.programSubOptionSet;
        
        vm.selectedProgramSubOption = _.find(vm.activeProgramSubOptionSet, function(item) {
          return item.routing_name === programSubOptionName;
        });
        
        if(vm.selectedProgramSubOption) {
          $log.log('-- selectedProgramSubOption success!', programSubOptionName);
          vm.selectedProgram.prevSelectedProgramSubOption = vm.selectedProgramSubOption;
          
          vm.activeMacroOptionSet = vm.selectedProgramSubOption.macroOptionSet;
          
          vm.selectedMacroOption = _.find(vm.activeMacroOptionSet, function(item) {
            return item.routing_name === macrosOptionName;
          });
          
          if(vm.selectedMacroOption) {
            $log.log('-- selectedMacroOption success!', macrosOptionName);
            vm.selectedProgramSubOption.prevSelectedMacroOption = vm.selectedMacroOption;
          }
          
        }
      }
      
      // if(!vm.selectedProgram) {
        
      // }
      
      // if(hasRoutingError) {
        
      // }
      
      // _selectedProgram = _.find(vm.programOptionSet, function(item) { return item.routing_name.toUpperCase() === vm.selectedProgramId.toUpperCase(); });
      // if(!_selectedProgram) {
      //   // $log.log('invalid program name');
      //   _selectedProgram = vm.programOptionSet[0];
      //   vm.selectedProgramId = _selectedProgram.programId;
      //   $timeout(function() {
      //     updateRoute();
      //   }, 10);
      // }
      //vm.selectedMacroOption = vm.selectedProgram.macroOptionSet[vm.selectedProgram.defaultMacroOptionIndex];
      
      
      // if(initializeFromRoute) {
      //   vm.activeProgramSubOptionSet = vm.selectedProgram.programSubOptionSet;
        
      //   vm.selectedProgramSubOption = _.find(vm.activeProgramSubOptionSet, function(item) {
      //     return item.routing_name === programSubOptionName;
      //   });
        
      //   if(vm.selectedProgramSubOption) {
          
      //     vm.activeMacroOptionSet = vm.selectedProgramSubOption.macroOptionSet;
          
      //     vm.selectedMacroOption = _.find(vm.activeMacroOptionSet, function(item) {
      //       return item.routing_name === macrosOptionName;
      //     });
          
      //   }
      // }
      
      
      // if (hasRoutingError) {
      //   vm.selectedProgramRoutingName = _DefaultStartupProgramRoutingName;
        
      //   onProgramOptionChange();
        
      //   updateRoute();
      // }
      
      onProgramOptionChange();
        
      updateRoute();
    }
    
    Object.defineProperty(vm, "shareLink", {
      get: function() {
        return $location.absUrl();
      }
    });
    
    Object.defineProperty(vm, "hasTrainingRestDaysSplit", {
      get: function() {
        return vm.selectedProgramSubOption.hasTrainingRestDaysSplit;
      }
    });
    
    Object.defineProperty(vm, "hasRefeedDaySubOption", {
      get: function() {
        return vm.selectedProgramSubOption.hasRefeedDaySubOption;
      }
    });
    
    Object.defineProperty(vm, "refeedDaySubOption", {
      get: function() {
        return vm.selectedProgramSubOption.refeedDaySubOption;
      }
    });
    
    Object.defineProperty(vm, "hasFixedCaloriesOffset", {
      get: function() {
        return vm.selectedProgramSubOption.hasFixedCaloriesOffset;
      }
    });
    
    Object.defineProperty(vm, "isGoalBodyWeightRequired", {
      get: function() {
        return vm.selectedProgramSubOption.isGoalBodyWeightRequired;
      }
    });
    
    Object.defineProperty(vm, "isBodyWeightOrGoalBodyWeightRequired", {
      get: function() {
        return vm.selectedProgramSubOption.isBodyWeightOrGoalBodyWeightRequired;
      }
    });
    
    Object.defineProperty(vm, "hasGoalNotes", {
      get: function() {
        return vm.selectedProgramSubOption.goalNotes && vm.selectedProgramSubOption.goalNotes.length > 0;
      }
    });
    
    Object.defineProperty(vm, "goalNotes", {
      get: function() {
        return vm.selectedProgramSubOption.goalNotes;
      }
    });
    
    Object.defineProperty(vm, "hasRefeedDayNotes", {
      get: function() {
        return vm.selectedProgramSubOption.refeedDayNotes && vm.selectedProgramSubOption.refeedDayNotes.length > 0;
      }
    });
    
    Object.defineProperty(vm, "refeedDayNotes", {
      get: function() {
        return vm.selectedProgramSubOption.refeedDayNotes;
      }
    });
    
    // Object.defineProperty(vm, "selectedProgram", {
    //   get: function() {
    //     return _selectedProgram;
    //   },
    //   set: function(value) {
    //     _selectedProgram = value;
    //     vm.selectedProgramName = _selectedProgram.routing_name;
    //   }
    // });
    
    Object.defineProperty(vm, "inputWeight", {
      get: function() {
        return _inputWeight;
      },
      set: function(value) {
        _inputWeight = value;
        vm.weightKg = convertInputWeightToKg(_inputWeight);
      }
    });
    
    Object.defineProperty(vm, "weightLbs", {
      get: function() {
        if(vm.weightKg) {
          return vm.weightKg * _kgToPoundRatio;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kcalOffset_number", {
      get: function() {
        if(vm.kcalOffset && canBeNumber(vm.kcalOffset)) {
          return Number(vm.kcalOffset);
        }
        return 0;
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories", {
      get: function() {
        if(vm.weightLbs) {
          return (kinobodyProgramData.getKinobodyMaintenanceFactor() * vm.weightLbs) + vm.kcalOffset_number;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateMaintenanceCalories_display", {
      get: function() {
        if(vm.kinobodyEstimateMaintenanceCalories) {
          return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "activeMaintenanceFormula", {
      get: function() {
        // => only use KB for now!
        if (vm.kinobodyEstimateMaintenanceCalories) {
          return 'KB';   
        }
        return '';
      }
    });
    
    // Object.defineProperty(vm, "maintenanceCalories", {
    //   get: function() {
    //     if (vm.kinobodyEstimateMaintenanceCalories) {
    //       return formatCalNumber(vm.kinobodyEstimateMaintenanceCalories);
    //     }
    //     return '';
    //   }
    // });
    
    Object.defineProperty(vm, "inputUnitsWeight_display", {
      get: function() {
        if (vm.inputUnitsWeight === 'kg') {
          return 'Kg';
        } else if (vm.inputUnitsWeight === 'lbs') {
          return 'Lbs';
        }
        return 'inputUnitsWeight_display error';
      }
    });
    
    Object.defineProperty(vm, "inputWeightLabel", {
      get: function() {
        if(vm.isGoalBodyWeightRequired && vm.isGoalBodyWeightRequired === true) {
          return 'Goal weight in ' + vm.inputUnitsWeight_display;
        } else if (vm.isBodyWeightOrGoalBodyWeightRequired) {
          return '(Goal/) Body weight in ' + vm.inputUnitsWeight_display;
        } else {
          return 'Weight in ' + vm.inputUnitsWeight_display;
        }
      }
    });
    
    //--------------------------------------------------------------------------
    // default / training days
    //--------------------------------------------------------------------------
    Object.defineProperty(vm, "macroSectionLabel", {
      get: function() {
        var label = 'Daily calories / macros';
        if(vm.hasTrainingRestDaysSplit) {
          label = 'Training days calories / macros';
        }
        return label;
      }
    });
    
    Object.defineProperty(vm, "dailyCaloriesLabel", {
      get: function() {
        var label = 'Calories ' 
          + '(lbs x ' + vm.selectedProgramSubOption.lbsFactor + ')'
          + vm.programOffsetLabel;
        return label;
      }
    });
    
    Object.defineProperty(vm, "programOffsetLabel", {
      get: function() {
        var offset = vm.programOffset;
        if (offset !== 0) {
          if(offset > 0) {
            return ' + ' + offset;
          } else {
            return ' - ' + Math.abs(offset);
          }  
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "programOffset", {
      get: function() {
        if (vm.hasTrainingRestDaysSplit) {
          return vm.selectedProgramSubOption.trainingDaysCaloriesOffset;
        } else if (vm.hasFixedCaloriesOffset) {
          return vm.selectedProgramSubOption.fixedCaloriesOffset;
        }
        return 0;
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories", {
      get: function() {
        if(vm.weightLbs) {
          return (vm.selectedProgramSubOption.lbsFactor * vm.weightLbs) + vm.programOffset + vm.kcalOffset_number;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_display", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories);
        }
        return '';
      }
    });
        
    
    Object.defineProperty(vm, "deficitCalories", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosProtein", {
      get: function() {
        if(vm.selectedMacroOption && vm.deficitCalories) {
          return formatCalNumber(vm.selectedMacroOption.getProteinMacros(vm.deficitCalories, vm.weightKg, vm.weightLbs));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosFats", {
      get: function() {
        if(vm.selectedMacroOption && vm.deficitCalories) {
          return formatCalNumber(vm.selectedMacroOption.getFatMacros(vm.deficitCalories));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosCarbs", {
      get: function() {
        // the rest from carbs
        if(vm.deficitCalories) {
          return formatCalNumber(kinobodyProgramData.calculateCarbMacros_default(vm.macrosProtein, vm.macrosFats, vm.deficitCalories));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageProtein", {
      get: function() {
        if(vm.macrosProtein) {
          var calsFats = vm.macrosProtein * 4;
          var p = calsFats / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageFats", {
      get: function() {
        if(vm.macrosFats) {
          var calsFats = vm.macrosFats * 9;
          var p = calsFats / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageCarbs", {
      get: function() {
        if(vm.macrosCarbs) {
          var calsCarbs = vm.macrosCarbs * 4;
          var p = calsCarbs / vm.deficitCalories * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "crossCheckCalories", {
      get: function() {
        if(vm.macrosProtein && vm.macrosFats && vm.macrosCarbs) {
          var calsProtein = vm.macrosProtein * 4;
          var calsFats = vm.macrosFats * 9;
          var calsCarbs = vm.macrosCarbs * 4;
          return formatCalNumber(calsProtein + calsFats + calsCarbs);
        }
        return '';
      }
    });
    
    //--------------------------------------------------------------------------
    // rest days
    //--------------------------------------------------------------------------
    Object.defineProperty(vm, "macroSectionLabel_rest", {
      get: function() {
        var label = 'Rest days calories / macros';
        return label;
      }
    });
    
    Object.defineProperty(vm, "dailyCaloriesLabel_rest", {
      get: function() {
        var label = 'Calories ' 
          + '(lbs x ' + vm.selectedProgramSubOption.lbsFactor + ')'
          + vm.programOffsetLabel_rest;
        return label;
      }
    });
    
    Object.defineProperty(vm, "programOffsetLabel_rest", {
      get: function() {
        if (vm.hasTrainingRestDaysSplit) {
          if(vm.selectedProgramSubOption.restDaysCaloriesOffset > 0) {
            return ' + ' + vm.selectedProgramSubOption.restDaysCaloriesOffset;
          } else {
            return ' - ' + Math.abs(vm.selectedProgramSubOption.restDaysCaloriesOffset);
          }
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_rest", {
      get: function() {
        if(vm.weightLbs) {
          if(!vm.hasTrainingRestDaysSplit) {
            return (vm.selectedProgramSubOption.lbsFactor * vm.weightLbs);
          } else {
            return (vm.selectedProgramSubOption.lbsFactor * vm.weightLbs) + vm.selectedProgramSubOption.restDaysCaloriesOffset + vm.kcalOffset_number;
          }
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_rest_display", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories_rest) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories_rest);
        }
        return '';
      }
    });    
    
    Object.defineProperty(vm, "deficitCalories_rest", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories_rest) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories_rest);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosProtein_rest", {
      get: function() {
        if(vm.selectedMacroOption && vm.deficitCalories_rest) {
          return formatCalNumber(vm.selectedMacroOption.getProteinMacros(vm.deficitCalories_rest, vm.weightKg, vm.weightLbs));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosFats_rest", {
      get: function() {
        if(vm.selectedMacroOption && vm.deficitCalories_rest) {
          return formatCalNumber(vm.selectedMacroOption.getFatMacros(vm.deficitCalories_rest));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosCarbs_rest", {
      get: function() {
        // the rest from carbs
        if(vm.deficitCalories_rest) {
          return formatCalNumber(kinobodyProgramData.calculateCarbMacros_default(vm.macrosProtein_rest, vm.macrosFats_rest, vm.deficitCalories_rest));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageProtein_rest", {
      get: function() {
        if(vm.macrosProtein) {
          var calsFats = vm.macrosProtein_rest * 4;
          var p = calsFats / vm.deficitCalories_rest * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageFats_rest", {
      get: function() {
        if(vm.macrosFats) {
          var calsFats = vm.macrosFats_rest * 9;
          var p = calsFats / vm.deficitCalories_rest * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageCarbs_rest", {
      get: function() {
        if(vm.macrosCarbs) {
          var calsCarbs = vm.macrosCarbs_rest * 4;
          var p = calsCarbs / vm.deficitCalories_rest * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "crossCheckCalories_rest", {
      get: function() {
        if(vm.macrosProtein_rest && vm.macrosFats_rest && vm.macrosCarbs_rest) {
          var calsProtein = vm.macrosProtein_rest * 4;
          var calsFats = vm.macrosFats_rest * 9;
          var calsCarbs = vm.macrosCarbs_rest * 4;
          return formatCalNumber(calsProtein + calsFats + calsCarbs);
        }
        return '';
      }
    });
    
    //--------------------------------------------------------------------------
    // refeed days
    //--------------------------------------------------------------------------
    Object.defineProperty(vm, "macroSectionLabel_refeed", {
      get: function() {
        var label = 'Refeed days calories / macros';
        if (vm.selectedProgramSubOption.refeedDayTitle) {
          label = vm.selectedProgramSubOption.refeedDayTitle;
        }
        return label;
      }
    });
    
    Object.defineProperty(vm, "dailyCaloriesLabel_refeed", {
      get: function() {
        if (vm.hasRefeedDaySubOption) {
          var label = 'Calories ' 
            + '(lbs x ' + vm.refeedDaySubOption.lbsFactor + ')';
          if(vm.refeedDaySubOption.refeedDaysCaloriesOffset && vm.refeedDaySubOption.refeedDaysCaloriesOffset !== 0) {
            label += ' ' + ((vm.refeedDaySubOption.refeedDaysCaloriesOffset > 0) ? '+' : '-') 
                     + ' ' + vm.refeedDaySubOption.refeedDaysCaloriesOffset;
          }
          return label;
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_refeed", {
      get: function() {
        if (vm.hasRefeedDaySubOption) {
          if(vm.weightLbs) {
            var offset = (vm.refeedDaySubOption.refeedDaysCaloriesOffset && vm.refeedDaySubOption.refeedDaysCaloriesOffset !== 0) 
              ? vm.refeedDaySubOption.refeedDaysCaloriesOffset
              : 0;
            return (vm.refeedDaySubOption.lbsFactor * vm.weightLbs) + offset + vm.kcalOffset_number;
          }
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "kinobodyEstimateDailyCalories_refeed_display", {
      get: function() {
        if (vm.hasRefeedDaySubOption) {
          if(vm.kinobodyEstimateDailyCalories_refeed) {
            return formatCalNumber(vm.kinobodyEstimateDailyCalories_refeed);
          }
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "deficitCalories_refeed", {
      get: function() {
        if(vm.kinobodyEstimateDailyCalories_refeed) {
          return formatCalNumber(vm.kinobodyEstimateDailyCalories_refeed);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "selectedRefeedMacroOption", {
      get: function() {
        if (vm.hasRefeedDaySubOption) {
          return vm.refeedDaySubOption.macroOptionSet[0];   // only one possible at the moment!
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosProtein_refeed", {
      get: function() {
        if(vm.selectedRefeedMacroOption && vm.deficitCalories_refeed) {
          return formatCalNumber(vm.selectedRefeedMacroOption.getProteinMacros(vm.deficitCalories_refeed, vm.weightKg, vm.weightLbs));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosFats_refeed", {
      get: function() {
        if(vm.selectedRefeedMacroOption && vm.deficitCalories_refeed) {
          return formatCalNumber(vm.selectedRefeedMacroOption.getFatMacros(vm.deficitCalories_refeed));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "macrosCarbs_refeed", {
      get: function() {
        // the rest from carbs
        if(vm.deficitCalories_refeed) {
          return formatCalNumber(kinobodyProgramData.calculateCarbMacros_default(vm.macrosProtein_refeed, vm.macrosFats_refeed, vm.deficitCalories_refeed));
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageProtein_refeed", {
      get: function() {
        if(vm.macrosProtein_refeed) {
          var calsFats = vm.macrosProtein_refeed * 4;
          var p = calsFats / vm.deficitCalories_refeed * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageFats_refeed", {
      get: function() {
        if(vm.macrosFats_refeed) {
          var calsFats = vm.macrosFats_refeed * 9;
          var p = calsFats / vm.deficitCalories_refeed * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "percentageCarbs_refeed", {
      get: function() {
        if(vm.macrosCarbs_refeed) {
          var calsCarbs = vm.macrosCarbs_refeed * 4;
          var p = calsCarbs / vm.deficitCalories_refeed * 100;
          return formatCalNumber(p);
        }
        return '';
      }
    });
    
    Object.defineProperty(vm, "crossCheckCalories_refeed", {
      get: function() {
        if(vm.macrosProtein_refeed && vm.macrosFats_refeed && vm.macrosCarbs_refeed) {
          var calsProtein = vm.macrosProtein_refeed * 4;
          var calsFats = vm.macrosFats_refeed * 9;
          var calsCarbs = vm.macrosCarbs_refeed * 4;
          return formatCalNumber(calsProtein + calsFats + calsCarbs);
        }
        return '';
      }
    });
    
    //--------------------------------------------------------------------------
    
    vm.formatCalNumber = formatCalNumber;
    vm.toggleInputUnitsWeight = toggleInputUnitsWeight;
    vm.onWeightChange = onWeightChange;
    vm.onKcalOffsetChange = onKcalOffsetChange;
    vm.onProgramOptionChange = onProgramOptionChange;
    vm.onProgramSubOptionChange = onProgramSubOptionChange;
    vm.onMacroOptionChange = onMacroOptionChange;
    vm.resetForm = resetForm;
    
    vm.onClipboardSuccess = onClipboardSuccess;
    vm.onClipboardFail = onClipboardFail;
    
    function onClipboardSuccess() {
      ngToast.create("Address copied to clipboard");
      // $log.log('onClipboardSuccess');
    }
    
    function onClipboardFail(err) {
      $log.log('onClipboardFail', err);
    }
    
    function onWeightChange() {
      updateRoute();
    }
    
    function onKcalOffsetChange() {
      if(canBeNumber(vm.kcalOffset)) {
        vm.kcalOffset = Number(vm.kcalOffset);
      }
      vm.kcalOffsetActive = true;
      updateRoute();
    }
    
    function onProgramOptionChange() {
      $log.log('onProgramOptionChange');
      
      if(vm.activeProgramSubOptionSet !== vm.selectedProgram.programSubOptionSet) {
        vm.activeProgramSubOptionSet = vm.selectedProgram.programSubOptionSet;
      }
  
      if(!!!vm.selectedProgram.prevSelectedProgramSubOption) {
        //$log.log('onProgramOptionChange AAA');
        vm.selectedProgramSubOption = vm.selectedProgram.programSubOptionSet[vm.selectedProgram.defaultProgramSubOptionIndex];
      } else {
        //$log.log('onProgramOptionChange BBB');
        vm.selectedProgramSubOption = vm.selectedProgram.prevSelectedProgramSubOption;
      }
      
      onProgramSubOptionChange();
      //updateRoute();
    }
    
    function onProgramSubOptionChange() {
      $log.log('onProgramSubOptionChange');
      
      vm.selectedProgram.prevSelectedProgramSubOption = vm.selectedProgramSubOption;
      
      vm.activeMacroOptionSet = vm.selectedProgramSubOption.macroOptionSet;
      //vm.selectedMacroOption = vm.activeMacroOptionSet[vm.selectedProgramSubOption.defaultMacroOptionIndex];
      
      if(!!!vm.selectedProgramSubOption.prevSelectedMacroOption) {
        //$log.log('onProgramSubOptionChange CCC');
        vm.selectedMacroOption = vm.activeMacroOptionSet[vm.selectedProgramSubOption.defaultMacroOptionIndex];
      } else {
        //$log.log('onProgramSubOptionChange DDD');
        vm.selectedMacroOption = vm.selectedProgramSubOption.prevSelectedMacroOption;
      }
      
      onMacroOptionChange();
      //updateRoute();
    }
    
    function onMacroOptionChange() {
      //$log.log('onMacroOptionChange EEE');
      vm.selectedProgramSubOption.prevSelectedMacroOption = vm.selectedMacroOption;
      
      updateRoute();
    }
    
    function resetForm() {
      vm.inputWeight = '';
      vm.kcalOffset = 0;
      $state.go('reset', {}, { notify: true });
    }
    
    function updateRoute() {
      if(!initialized || vm.inputWeight === '') 
        return;
      
      $log.log('updateRoute XXX');
      
      // $log.log('updateRoute', vm.storage, $location, $location.absUrl());
      var stateOptions = {
        programName: vm.selectedProgram.routing_name,
        programSubOptionName: vm.selectedProgramSubOption.routing_name,
        macrosOptionName: vm.selectedMacroOption.routing_name,
        weight: vm.inputWeight,
        weightUnits: vm.inputUnitsWeight,
      };
      if(vm.kcalOffset_number !== 0 || vm.kcalOffsetActive) {
        stateOptions.kcalOffset = vm.kcalOffset_number;
      }
      $log.log('updateRoute', stateOptions);
      $state.transitionTo('home', stateOptions, { notify: false });
    }
    
    function canBeNumber(i) {
      if(_.isNumber(i)) {
        return true;
      } 
      if(Number.isNaN(Number(i))) {
        return false;
      }
      return true;
    }
    
    function makeNumber(i) {
      return Number(i);
    }
    
    function formatCalNumber(i) {
      if(i === '' || !canBeNumber(i)) {
        return '';
      }
      return Number(i).toFixed(0);
    }
    
    function toggleInputUnitsWeight() {
      // $log.log('toggleInputUnitsWeight');
      switch(vm.inputUnitsWeight){
        case 'kg':
          // LBS
          vm.inputUnitsWeight = 'lbs';
          vm.inputWeight = vm.inputWeight * _kgToPoundRatio;
          break;
        case 'lbs':
          // KG
          vm.inputUnitsWeight = 'kg';
          vm.inputWeight = vm.inputWeight / _kgToPoundRatio;
          break;
      }
      updateRoute();
    }
    
    function convertInputWeightToKg(inputWeight) {
      if(vm.inputUnitsWeight === 'kg') {
        return inputWeight;
      } else {
        return inputWeight / _kgToPoundRatio;
      }
    }
    
    // state change event handler
    $scope.$on("$stateChangeStart",
      function (event, toState, toParams, fromState, fromParams) {
        $log.log('$stateChangeStart', $location);
        $log.log('  event', event);
        $log.log('  toParams', toParams);
        
        processStateParams(toParams);
      }
    );
    
    init();
  }
  
})();
Kinobody Calories / Macros 

  - Kinobody easy formulas for maintenance/deficit calculations
  - Creates url that can be forum-posted:
      http://run.plnkr.co/plunks/p1p1tI/#/kinobody_ggp/afl_nutrition/macros_35p_30f_35c/165_lbs
      http://run.plnkr.co/plunks/p1p1tI/#/kinobody_ggp/lean_bulk/macros_1p_25f/73_kg?kcalOffset=200
   
Todo:
  - verify everything is correct
  - add routing again
  - reorganize code (move "data" into own module)

  
Bugs: 
  - 
Useful links for this plunk:

https://github.com/omichelsen/angular-clipboard
http://tamerayd.in/ngToast/

https://www.niclassahlin.com/2014/05/31/kickstarting-angular-ui-router/

http://www.metric-conversions.org/length/centimeters-to-inches.htm
http://www.metric-conversions.org/weight/kilograms-to-pounds.htm
https://github.com/gsklee/ngStorage
https://scotch.io/tutorials/the-many-ways-to-use-ngclass

// fix enter key strange behaviour:
http://stackoverflow.com/a/11591419/54159 : Just add the type="button" attribute to the button element, some browsers interpret the type as submit by default.
http://stackoverflow.com/questions/10400149/avoid-modal-dismiss-on-enter-keypress

https://gist.github.com/demisx/9605099
http://benfoster.io/blog/ui-router-optional-parameters
(function (root, factory) {
    /* istanbul ignore next */
    if (typeof define === 'function' && define.amd) {
        define(['angular'], factory);
    } else if (typeof module === 'object' && module.exports) {
        module.exports = factory(require('angular'));
    } else {
        root.angularClipboard = factory(root.angular);
  }
}(this, function (angular) {

return angular.module('angular-clipboard', [])
    .factory('clipboard', ['$document', function ($document) {
        function createNode(text) {
            var node = $document[0].createElement('textarea');
            node.style.position = 'absolute';
            node.style.left = '-10000px';
            node.textContent = text;
            return node;
        }

        function copyNode(node) {
            try {
                // Set inline style to override css styles
                $document[0].body.style.webkitUserSelect = 'initial';

                var selection = $document[0].getSelection();
                selection.removeAllRanges();
                node.select();

                if(!$document[0].execCommand('copy')) {
                    throw('failure copy');
                }
                selection.removeAllRanges();
            } finally {
                // Reset inline style
                $document[0].body.style.webkitUserSelect = '';
            }
        }

        function copyText(text) {
            var node = createNode(text);
            $document[0].body.appendChild(node);
            copyNode(node);
            $document[0].body.removeChild(node);
        }

        return {
            copyText: copyText,
            supported: 'queryCommandSupported' in document && document.queryCommandSupported('copy')
        };
    }])
    .directive('clipboard', ['clipboard', function (clipboard) {
        return {
            restrict: 'A',
            scope: {
                onCopied: '&',
                onError: '&',
                text: '=',
                supported: '='
            },
            link: function (scope, element) {
                scope.supported = clipboard.supported;

                element.on('click', function (event) {
                    try {
                        clipboard.copyText(scope.text);
                        if (angular.isFunction(scope.onCopied)) {
                            scope.$evalAsync(scope.onCopied());
                        }
                    } catch (err) {
                        if (angular.isFunction(scope.onError)) {
                            scope.$evalAsync(scope.onError({err: err}));
                        }
                    }
                });
            }
        };
    }]);

}));
(function kinobodyProgramDataServiceIIFE() {
    'use strict';

    var factoryId = 'kinobodyProgramData';

    var app = angular.module('app');

    app.factory(factoryId, ['$log', KinobodyProgramData]);

    function KinobodyProgramData($log) {

        var service = {};

        service.getKinobodyPrograms = getKinobodyPrograms;
        service.getKinobodyMaintenanceFactor = getKinobodyMaintenanceFactor;
        service.calculateProteinMacros_default = calculateProteinMacros_default;
        service.calculateFatMacros_default = calculateFatMacros_default;
        service.calculateCarbMacros_default = calculateCarbMacros_default;
        
        //-----------------------------------------------------------------
        // program specific defaults
        //-----------------------------------------------------------------
        var _kinobodyMaintenanceFactor = 15;
        var _AflDefaultLbsFactor = 11;
        var _AflDefaultRefeedDayLbsFactor = _kinobodyMaintenanceFactor;
        var _WspDefaultLbsFactor = 12;
        var _WspDefaultRefeedDayLbsFactor = _kinobodyMaintenanceFactor;
        var _GtpDefaultLbsFactor = 12;
        var _GtpDefaultRefeedDayLbsFactor = _GtpDefaultLbsFactor;
        var _SbpDefaultUnderfeedDayLbsFactor = _kinobodyMaintenanceFactor;
        
        var _AflDefaultMacroOptionSet = [
          {
            routing_name: 'macros_35p_30f_35c',
            name: '35% protein, 30% fat, 35% carbs (default)', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.35);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
          {
            routing_name: 'macros_35p_25f_40c',
            name: '35% protein, 25% fat, 40% carbs', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.35);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.25);
            },
          },
          {
            routing_name: 'macros_30p_30f_40c',
            name: '30% protein, 30% fat, 40% carbs', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return calculateProteinMacros_default(calories, 0.30);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
        ];
        var _WspDefaultMacroOptionSet = [
          {
            routing_name: 'macros_30p_30f_40c',
            name: '30% protein, 30% fat, 40% carbs (default)', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.30);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
        ];
        var _GgpDefaultMacroOptionSet = [
          {
            routing_name: 'macros_09p_25f',
            name: 'Default protein (0.9 g/lbs, 2 g/kg)', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return 2 * weightKg;
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.25);
            },
          },
          {
            routing_name: 'macros_08p_25f',
            name: 'Less protein (0.8 g/lb)', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return 0.8 * weightLbs;
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.25);
            },
          },
          {
            routing_name: 'macros_1p_25f',
            name: 'More protein (1 g/lb)', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return 1 * weightLbs;
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.25);
            },
          },
          {
            routing_name: 'macros_30p_30f_40c',
            name: '30% protein, 30% fat, 40% carbs', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return calculateProteinMacros_default(calories, 0.30);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
        ];
        var _SbpDefaultMacroOptionSet = [
          {
            routing_name: 'macros_25p_30f_45c',
            name: '25% protein, 30% fat, 45% carbs (default)', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return calculateProteinMacros_default(calories, 0.25);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
          {
            routing_name: 'macros_30p_30f_40c',
            name: '30% protein, 30% fat, 40% carbs', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return calculateProteinMacros_default(calories, 0.30);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          }
        ];
        var _GtpDefaultMacroOptionSet = [
          {
            routing_name: 'macros_35p_25f_40c',
            name: '35% protein, 25% fat, 40% carbs', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.35);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.25);
            },
          },
          {
            routing_name: 'macros_35p_27f_38c',
            name: '35% protein, 27.5% fat, 37.5% carbs (default)', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.35);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.275);
            },
          },
          {
            routing_name: 'macros_35p_30f_35c',
            name: '35% protein, 30% fat, 35% carbs', 
            getProteinMacros: function(calories) {
              return calculateProteinMacros_default(calories, 0.35);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
          {
            routing_name: 'macros_30p_30f_40c',
            name: '30% protein, 30% fat, 40% carbs', 
            getProteinMacros: function(calories, weightKg, weightLbs) {
              return calculateProteinMacros_default(calories, 0.30);
            },
            getFatMacros: function(calories) {
              return calculateFatMacros_default(calories, 0.30);
            },
          },
        ];
        
        var _AflDefaultRefeedDaySubOption = {
          routing_name: 'refeed_sub_option',
          name: 'refeed_sub_option',
          lbsFactor: _AflDefaultRefeedDayLbsFactor,
          defaultMacroOptionIndex: 0, 
          macroOptionSet: [
            {
              routing_name: 'refeed_default_macros',
              name: 'refeed_default_macros', 
              getProteinMacros: function(calories) {
                return calculateProteinMacros_default(calories, 0.25);
              },
              getFatMacros: function(calories) {
                return calculateFatMacros_default(calories, 0.25);
              },
            }
          ],
        };
        var _WspDefaultRefeedDaySubOption = {
          routing_name: 'refeed_sub_option',
          name: 'refeed_sub_option',
          lbsFactor: _WspDefaultRefeedDayLbsFactor,
          defaultMacroOptionIndex: 0, 
          macroOptionSet: [
            {
              routing_name: 'refeed_default_macros',
              name: 'refeed_default_macros', 
              getProteinMacros: function(calories) {
                return calculateProteinMacros_default(calories, 0.25);
              },
              getFatMacros: function(calories) {
                return calculateFatMacros_default(calories, 0.25);
              },
            }
          ],
        };
        var _SbpDefaultUnderfeedDaySubOption = {
          routing_name: 'underfeed_sub_option',
          name: 'underfeed_sub_option',
          lbsFactor: _SbpDefaultUnderfeedDayLbsFactor,
          defaultMacroOptionIndex: 0, 
          macroOptionSet: [
            {
              routing_name: 'underfeed_default_macros',
              name: 'underfeed_default_macros', 
              getProteinMacros: function(calories) {
                return calculateProteinMacros_default(calories, 0.25);
              },
              getFatMacros: function(calories) {
                return calculateFatMacros_default(calories, 0.25);
              },
            }
          ],
        };
        var _GtpDefaultRefeedDaySubOption = {
          routing_name: 'refeed_sub_option',
          name: 'refeed_sub_option',
          lbsFactor: _GtpDefaultRefeedDayLbsFactor,
          defaultMacroOptionIndex: 0, 
          refeedDaysCaloriesOffset: 600,
          macroOptionSet: [
            {
              routing_name: 'refeed_default_macros',
              name: 'refeed_default_macros', 
              getProteinMacros: function(calories) {
                return calculateProteinMacros_default(calories, 0.25);
              },
              getFatMacros: function(calories) {
                return calculateFatMacros_default(calories, 0.25);
              },
            }
          ],
        };
        
        //-----------------------------------------------------------------
        // Kinobody Program Configs
        //-----------------------------------------------------------------
        var kinobodyPrograms = [
          { 
            routing_name: 'kinobody_afl',
            name: 'Kinobody AFL',
            description: 'Aggressive Fat Loss',
            programSubOptionSet: [
              {
                routing_name: 'default',
                name: 'Default',
                lbsFactor: _AflDefaultLbsFactor,
                defaultMacroOptionIndex: 0,
                macroOptionSet: _AflDefaultMacroOptionSet,
                isGoalBodyWeightRequired: true,
                goalNotes: [
                  { text: 'Adjust goal weight in 20 lbs decrements' },
                  { text: 'For example, if current body weight is 200 lbs, use goal weight 180 lbs; when reaching that, use 160 lbs, etc.' }, 
                ],
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _AflDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'One refeed day per week' },
                ],
              },
            ],
            defaultProgramSubOptionIndex: 0,
          },
          { 
            routing_name: 'kinobody_wsp',
            name: 'Kinobody WSP',
            description: 'Warrior Shredding Program',
            programSubOptionSet: [
              {
                routing_name: 'default',
                name: 'Default',
                lbsFactor: _WspDefaultLbsFactor,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _WspDefaultMacroOptionSet,
                isBodyWeightOrGoalBodyWeightRequired: true,
                goalNotes: [
                  { text: 'If you have 15 lbs / 7 kg or less to lose, use body weight' }, 
                  { text: 'If you have more than 15 lbs / 7 kg to lose, use GOAL body weight' }, 
                ],
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _WspDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'Recommendation: 2 refeed days per week,' },
                  { text: 'Separated by at least 2 days' },
                ],
              },
            ],
            defaultProgramSubOptionIndex: 0,
          },
          { 
            routing_name: 'kinobody_ggp',
            name: 'Kinobody GGP',
            description: 'Greek God Program',
            programSubOptionSet: [
              {
                routing_name: 'lean_bulk',
                name: 'Lean Bulk',
                lbsFactor: _kinobodyMaintenanceFactor,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _GgpDefaultMacroOptionSet,
                hasTrainingRestDaysSplit: true,
                trainingDaysCaloriesOffset: 500,
                restDaysCaloriesOffset: 100,
              },
              {
                routing_name: 'recomp',
                name: 'Recomp',
                lbsFactor: _kinobodyMaintenanceFactor,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _GgpDefaultMacroOptionSet,
                hasTrainingRestDaysSplit: true,
                trainingDaysCaloriesOffset: 400,
                restDaysCaloriesOffset: -300,
              },
              {
                routing_name: 'afl_nutrition',
                name: 'AFL Nutrition',
                lbsFactor: _AflDefaultLbsFactor,
                macroOptionSet: _AflDefaultMacroOptionSet,
                defaultMacroOptionIndex: 0, 
                isGoalBodyWeightRequired: true,
                goalNotes: [
                  { text: 'Adjust goal weight in 20 lbs decrements' },
                  { text: 'For example, if current body weight is 200 lbs, use goal weight 180 lbs; when reaching that, use 160 lbs, etc.' }, 
                ],
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _AflDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'One refeed day per week' },
                ],
              },
            ],
            defaultProgramSubOptionIndex: 0,
          },
          {
            routing_name: 'kinobody_sbp',
            name: 'Kinobody SPB',
            description: 'Superhero Bulking Program',
            programSubOptionSet: [
              {
                routing_name: 'default',
                name: 'default',
                lbsFactor: _kinobodyMaintenanceFactor,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _SbpDefaultMacroOptionSet,
                hasFixedCaloriesOffset: true,
                fixedCaloriesOffset: 300,
                hasRefeedDaySubOption: true,
                refeedDayTitle: 'Underfeed days calories / macros',
                refeedDaySubOption: _SbpDefaultUnderfeedDaySubOption,
                refeedDayNotes: [
                  { text: 'Recommendation: 1 or 2 underfeed days per week' },
                  //{ text: 'Separated by at least 2 days' },
                ],
              },
            ],
            defaultProgramSubOptionIndex: 0,
          },
          {
            routing_name: 'kinobody_gtp',
            name: 'Kinobody GTP',
            description: 'Goddess Toning Program',
            programSubOptionSet: [
              {
                routing_name: 'default',
                name: 'GTP Nutrition (default)',
                lbsFactor: _GtpDefaultLbsFactor,
                defaultMacroOptionIndex: 1, 
                macroOptionSet: _GtpDefaultMacroOptionSet,
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _GtpDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'Recommendation: One refeed day per week' },
                ],
              },
              {
                routing_name: 'afl_nutrition_10',
                name: 'AFL Nutrition (11 calories / lb)',
                lbsFactor: 11,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _AflDefaultMacroOptionSet,
                isGoalBodyWeightRequired: true,
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _GtpDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'Recommendation: One refeed day per week' },
                ],
              },
              {
                routing_name: 'afl_nutrition_10',
                name: 'AFL Nutrition (10 calories / lb)',
                lbsFactor: 10,
                defaultMacroOptionIndex: 0, 
                macroOptionSet: _AflDefaultMacroOptionSet,
                isGoalBodyWeightRequired: true,
                hasRefeedDaySubOption: true,
                refeedDaySubOption: _GtpDefaultRefeedDaySubOption,
                refeedDayNotes: [
                  { text: 'Recommendation: One refeed day per week' },
                ],
              },
            ],
            defaultProgramSubOptionIndex: 0,
          },
        ];
        
        //-----------------------------------------------------------------
        // public
        //-----------------------------------------------------------------
        function getKinobodyPrograms() {
          return kinobodyPrograms;
        }
        
        function getKinobodyMaintenanceFactor() {
          return _kinobodyMaintenanceFactor;
        }
        
        function calculateProteinMacros_default(calories, percentageValue) {
          //return roundToNearestX(percentageValue * calories / 4, 5);
          return percentageValue * calories / 4;
        }
        
        function calculateFatMacros_default(calories, percentageValue) {
          return percentageValue * calories / 9;
        }
        
        function calculateCarbMacros_default(macrosProtein, macrosFats, deficitCalories) {
          // rest carbs
          var carbGrams = 0;
          var calsProtein = macrosProtein * 4;
          var calsFats = macrosFats * 9;
          var calsPF = calsProtein + calsFats;
          var calsCarbs = deficitCalories - calsPF;
          carbGrams = calsCarbs / 4;
          // $log.log('carbGrams', carbGrams);
          return carbGrams;
        }
        
        //-----------------------------------------------------------------
        // private
        //-----------------------------------------------------------------
        function roundToNearestX(val, x) {
          return Math.ceil(val/x)*x;
        }

        return service;
    }
    
})();