/**
   * Setting up the MATSim {@link Config} so that it runs 20 iterations for a commercial vehicle
   * population.
   *
   * @param folder the specific folder to which the output will be written. Also, it assumes the
   *     following files are available as input in the folder:
   *     <ul>
   *       <li>{@code population.xml.gz} of commercial vehicles, each person with a plan. Only
   *           activity locations are required. The {@link Leg}s are allowed to take {@code
   *           commercial} as mode.
   *       <li>{@code populationAttributes.xml.gz} indicating which individuals are part of the
   *           <i>commercial vehicle</i> subpopulation. For that the only attribute required, namely
   *           {@code subpopulation}, needs to be set as {@code commercial}.
   *     </ul>
   *
   * @param machine the computer on which the run is executed. This just sets up the number of
   *     threads without having to hard code it in the class. The following values are currently
   *     supported:
   *     <ul>
   *       <li>{@code HOBBES} using 40 threads;
   *       <li>{@code MAC_MINI} the machine in Engineering 2, Room 3-13.1, a dual core i7 using 4
   *           threads;
   *       <li>{@code MACBOOK_PRO} is Johan W. Joubert's laptop, a dual core i5 using 4 threads.
   *     </ul>
   *
   * @return
   */
  public static Config setupConfig(String folder, Machine machine) {
    Config config = ConfigUtils.createConfig();

    /* Set global settings. */
    config.global().setNumberOfThreads(machine.getThreads());
    config.global().setCoordinateSystem("WGS84_SA_Albers");

    /* Set files and folders. */
    config.controler().setOutputDirectory(folder + "output/");
    config.controler().setFirstIteration(0);
    config.controler().setLastIteration(100);
    config.controler().setWriteEventsInterval(20);

    /* Network. */
    config.network().setInputFile(folder + "network.xml.gz");

    /* Population */
    config.plans().setInputFile(folder + "population.xml.gz");
    config.plans().setInputPersonAttributeFile(folder + "populationAttributes.xml.gz");
    config
        .plans()
        .setActivityDurationInterpretation(
            PlansConfigGroup.ActivityDurationInterpretation.tryEndTimeThenDuration);

    /* Facilities */
    config.facilities().setInputFile(folder + "facilities.xml.gz");

    /* QSim */
    config.qsim().setNumberOfThreads(machine.getThreads());
    String[] modes = {"car", "commercial"};
    config.qsim().setMainModes(Arrays.asList(modes));
    config.plansCalcRoute().setNetworkModes(Arrays.asList(modes));

    /* PlanCalcScore */
    ActivityParams major = new ActivityParams("major");
    major.setTypicalDuration(10 * 3600);
    config.planCalcScore().addActivityParams(major);

    ActivityParams minor = new ActivityParams("minor");
    minor.setTypicalDuration(1880);
    config.planCalcScore().addActivityParams(minor);

    /* Generic strategy */
    StrategySettings changeExpBetaStrategySettings =
        new StrategySettings(ConfigUtils.createAvailableStrategyId(config));
    changeExpBetaStrategySettings.setStrategyName(
        DefaultPlanStrategiesModule.DefaultSelector.ChangeExpBeta.toString());
    changeExpBetaStrategySettings.setWeight(0.8);
    config.strategy().addStrategySettings(changeExpBetaStrategySettings);
    /* Subpopulation strategy. */
    StrategySettings commercialStrategy =
        new StrategySettings(ConfigUtils.createAvailableStrategyId(config));
    commercialStrategy.setStrategyName(
        DefaultPlanStrategiesModule.DefaultSelector.ChangeExpBeta.toString());
    commercialStrategy.setWeight(0.85);
    commercialStrategy.setSubpopulation("commercial");
    config.strategy().addStrategySettings(commercialStrategy);
    /* Subpopulation ReRoute. Switch off after a time. */
    StrategySettings commercialReRoute =
        new StrategySettings(ConfigUtils.createAvailableStrategyId(config));
    commercialReRoute.setStrategyName(DefaultPlanStrategiesModule.DefaultStrategy.ReRoute.name());
    commercialReRoute.setWeight(0.15);
    commercialReRoute.setSubpopulation("commercial");
    commercialReRoute.setDisableAfter(85);
    config.strategy().addStrategySettings(commercialReRoute);

    return config;
  }
  public static void main(String[] args) {
    // see an example with detailed explanations -- package
    // opdytsintegration.example.networkparameters.RunNetworkParameters
    Config config = ConfigUtils.loadConfig(EQUIL_DIR + "/config.xml");

    config.controler().setOutputDirectory(OUT_DIR);
    config.controler().setOverwriteFileSetting(OverwriteFileSetting.deleteDirectoryIfExists);

    //		config.plans().setInputFile("relaxed_plans.xml.gz");
    config.plans().setInputFile("plans2000.xml.gz");

    // == default config has limited inputs
    StrategyConfigGroup strategies = config.strategy();
    strategies.clearStrategySettings();

    config.changeMode().setModes(new String[] {"car", "pt"});
    StrategySettings modeChoice = new StrategySettings();
    modeChoice.setStrategyName(
        DefaultPlanStrategiesModule.DefaultStrategy.ChangeSingleTripMode.name());
    modeChoice.setWeight(0.1);
    config.strategy().addStrategySettings(modeChoice);

    StrategySettings expChangeBeta = new StrategySettings();
    expChangeBeta.setStrategyName(DefaultPlanStrategiesModule.DefaultSelector.ChangeExpBeta.name());
    expChangeBeta.setWeight(0.9);
    config.strategy().addStrategySettings(expChangeBeta);

    for (PlanCalcScoreConfigGroup.ActivityParams params :
        config.planCalcScore().getActivityParams()) {
      params.setTypicalDurationScoreComputation(
          PlanCalcScoreConfigGroup.TypicalDurationScoreComputation.relative);
    }

    //		config.qsim().setTrafficDynamics( QSimConfigGroup.TrafficDynamics.withHoles );
    //
    //		if ( config.qsim().getTrafficDynamics()== QSimConfigGroup.TrafficDynamics.withHoles ) {
    //			config.qsim().setInflowConstraint(QSimConfigGroup.InflowConstraint.maxflowFromFdiag);
    //		}

    config.qsim().setUsingFastCapacityUpdate(true);

    // ==

    Scenario scenario = KNBerlinControler.prepareScenario(true, false, config);

    double time = 6 * 3600.;
    for (Person person : scenario.getPopulation().getPersons().values()) {
      Plan plan = person.getSelectedPlan();
      Activity activity = (Activity) plan.getPlanElements().get(0);
      activity.setEndTime(time);
      time++;
    }

    // ==

    // this is something like time bin generator
    int startTime = 0;
    int binSize = 3600; // can this be scenario simulation end time.
    int binCount = 24; // to me, binCount and binSize must be related
    TimeDiscretization timeDiscretization = new TimeDiscretization(startTime, binSize, binCount);

    Set<String> modes2consider = new HashSet<>();
    modes2consider.add("car");
    modes2consider.add("bike");

    OpdytsModalStatsControlerListener stasControlerListner =
        new OpdytsModalStatsControlerListener(modes2consider, EQUIL);

    // following is the  entry point to start a matsim controler together with opdyts
    MATSimSimulator<ModeChoiceDecisionVariable> simulator =
        new MATSimSimulator<>(new MATSimStateFactoryImpl<>(), scenario, timeDiscretization);
    simulator.addOverridingModule(
        new AbstractModule() {

          @Override
          public void install() {
            // add here whatever should be attached to matsim controler
            // some stats
            addControlerListenerBinding().toInstance(stasControlerListner);

            // from KN
            addControlerListenerBinding().to(KaiAnalysisListener.class);
            bind(CharyparNagelScoringParametersForPerson.class)
                .to(EveryIterationScoringParameters.class);
          }
        });

    // this is the objective Function which returns the value for given SimulatorState
    // in my case, this will be the distance based modal split
    ObjectiveFunction objectiveFunction =
        new ModeChoiceObjectiveFunction(
            EQUIL); // in this, the method argument (SimulatorStat) is not used.

    // search algorithm
    int maxIterations =
        10; // this many times simulator.run(...) and thus controler.run() will be called.
    int maxTransitions = Integer.MAX_VALUE;
    int populationSize =
        10; // the number of samples for decision variables, one of them will be drawn randomly for
    // the simulation.

    boolean interpolate = true;
    boolean includeCurrentBest = false;

    // randomize the decision variables (for e.g.\ utility parameters for modes)
    DecisionVariableRandomizer<ModeChoiceDecisionVariable> decisionVariableRandomizer =
        new ModeChoiceRandomizer(scenario, RandomizedUtilityParametersChoser.ONLY_ASC, EQUIL);

    // what would be the decision variables to optimize the objective function.
    ModeChoiceDecisionVariable initialDecisionVariable =
        new ModeChoiceDecisionVariable(scenario.getConfig().planCalcScore(), scenario, EQUIL);

    // what would decide the convergence of the objective function
    final int iterationsToConvergence = 200; //
    final int averagingIterations = 10;
    ConvergenceCriterion convergenceCriterion =
        new FixedIterationNumberConvergenceCriterion(iterationsToConvergence, averagingIterations);

    RandomSearch<ModeChoiceDecisionVariable> randomSearch =
        new RandomSearch<>(
            simulator,
            decisionVariableRandomizer,
            initialDecisionVariable,
            convergenceCriterion,
            maxIterations, // this many times simulator.run(...) and thus controler.run() will be
            // called.
            maxTransitions,
            populationSize,
            MatsimRandom.getRandom(),
            interpolate,
            objectiveFunction,
            includeCurrentBest);

    // probably, an object which decide about the inertia
    SelfTuner selfTuner = new SelfTuner(0.95);

    randomSearch.setLogPath(OUT_DIR);

    // run it, this will eventually call simulator.run() and thus controler.run
    randomSearch.run(selfTuner);
  }