package genesom;

import java.util.Properties;
import java.util.Enumeration;
import java.lang.reflect.Field;

public class SOM
{
  int cycles;
  int maxcycles;
  int testlength;
  int presentations;
  int maxpresentations;
  int moduloprint = Integer.MAX_VALUE;

  public Map map;

  Node nodes[];
  String presents;
  InputPatterns patterns;
  ShuffleIndex shuffle_index;
  WinnerVisitor winner_visitor;
  double patternmaxdistance = -1.;
  WeightsDeltaVisitor weights_delta_visitor;
//  WeightsDeltaNullVisitor weights_delta_null_visitor;
  WeightsInitVisitor weights_init_visitor;

  public WeightsInitFunction weights_init_function;  // externally assigned
  public WeightsDeltaFunction weights_delta_function;
  public NeighbourhoodFunction neighbourhood_function;
  public ProportionContextFunction proportion_context_function;


  public SOM(Map m) {

    map = m;

    setWinnerUpdate();
    setNodeUpdate();
    setWeightsInit();

    proportion_context_function = new ProportionContextFunction();
  }


  public void setPresentations(String presentations) {
    presents = presentations;
  }

  public void setModuloprint(int modprnt) {
    moduloprint = modprnt;
  }

  public void setProportion_pattern_context(double prop) {

    if(prop > 1.) prop = 1.;
    if(prop < 0.) prop = 0.;

    proportion_context_function.setPattern_context_proportion_model(
      "ConstantModel"
    );

    proportion_context_function.getDecayModel().setTo(prop);
  }

  public void setPatternmaxdistance(double patternmaxdistance) {
    this.patternmaxdistance = patternmaxdistance;
  }

  public void setTestlength(int tstlen) {
    testlength = tstlen;
  }


  public void setWinnerUpdate() {
    neighbourhood_function = new NeighbourhoodFunction();
    winner_visitor = new WinnerVisitor();
    winner_visitor.setCallback(neighbourhood_function);
    winner_visitor.setMap(map);
  }

  public void setNodeUpdate() {
    weights_delta_function = new WeightsDeltaFunction();
    weights_delta_visitor = new WeightsDeltaVisitor();
    weights_delta_visitor.setCallback(weights_delta_function);
    weights_delta_visitor.setMap(map);
    // weights_delta_null_visitor = new WeightsDeltaNullVisitor();
  }

  public void setWeightsInit() {
    weights_init_function = new WeightsInitFunction();
    weights_init_visitor = new WeightsInitVisitor();
    weights_init_visitor.setCallback(weights_init_function);
    weights_init_visitor.setMap(map);
  }


  public void setInputPatterns(InputPatterns inp) { patterns = inp; }


  public void init()
  {

    nodes = map.getNodes();
    map.setInput(new double[patterns.numberOfColumns()]);

    // do some lazy evaluation

    boolean cycles = presents.indexOf('C') >= 0 || presents.indexOf('c') >= 0;

    maxpresentations = new Integer(
        presents.substring(0, presents.length() - (cycles ? 1 : 0))).intValue();

    maxcycles = maxpresentations;

    if(cycles) {
      maxpresentations *= patterns.numberOfRows();
      testlength *= patterns.numberOfRows();
    }

    if(maxpresentations > 0) {
      neighbourhood_function.setSOMEnviroment(this);
      weights_delta_function.setSOMEnviroment(this);
      proportion_context_function.setSOMEnviroment(this);
    }
    else {

      weights_delta_function.setDelta_decay_model("ConstantModel");
      weights_delta_function.getDecayModel().setTo(0.);
      weights_delta_function.setSOMEnviroment(this);

      proportion_context_function.setPattern_context_proportion_model(
        "ConstantModel"
      );
      proportion_context_function.getDecayModel().setTo(
          proportion_context_function.getTo()
      );
      proportion_context_function.setSOMEnviroment(this);
    }

    map.setInputMaxDistance(patternmaxdistance);
  }


  public void reset() {
    map.setVisitor(weights_init_visitor);
    map.visit();
  }


  public boolean trainCycle()
  {
    ++cycles;

    for(int i = patterns.numberOfRows(); i > 0; i--) {

      ++presentations;

      double[] inp = patterns.next();

      if(inp == null || presentations > maxpresentations) return true;

      Node.proportion_pattern_context = proportion_context_function.value();

      map.setInput(inp);

      map.setVisitor(weights_delta_visitor);
      map.visit();


      double min = Double.MAX_VALUE;
      int winner = 0;

      for(int j = nodes.length - 1; j >= 0; j--) {

        double d = nodes[j].distance();

        if(d < min) {
          min = d;
          winner = j;
        }
      }

      map.setVisitor(winner_visitor);
      map.visit(winner);
      map.setWinner(nodes[winner]);
    }

    return false;
  }


  public void train(int cycles) {

    for(int i = 0; i < cycles; i++) {


      if(i % moduloprint == 0) {

        map.printAll();
        System.err.println("#" + i + " ::: " + map.inputmaxdistance);
        System.out.println();
        System.out.println();
      }

      if(trainCycle()) break;
    }
  }


  public void train() { train(maxcycles); }


  public void test()
  {

    // constant!
    Node.proportion_pattern_context = proportion_context_function.value();

    if(testlength < 0) testlength = Integer.MAX_VALUE;

    for(int i = 0; i < testlength; i++) {

      double[] inp = patterns.next();

      if(inp == null) return;

      map.setInput(inp);

      map.setVisitor(weights_delta_visitor);  // for distance calculation
      map.visit();

      double min = Double.MAX_VALUE;
      int winner = 0;

      for(int j = nodes.length - 1; j >= 0; j--) {

        double d = nodes[j].distance();

        if(d < min) {
          min = d;
          winner = j;
        }
      }

      Node win = nodes[winner], old = map.getWinner();

      map.setWinner(win);

      System.out.print("## " + i + ". ");

      for(int j = 0; j < inp.length; j++)
        System.out.print(inp[j] + " ");

      System.out.println(win + " " + win.context + " " +
                         map.nodeDistance(old, win) + " " + win.id);
    }
  }


  public void output() { map.printAll(); }
}
