//   Match 2.0.1 -- aligns data series using dynamic programming
//   Copyright (C) 2001-2003  Lorraine E. Lisiecki and Philip A. Lisiecki 
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; version 2
//of the License.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
//02111-1307, USA.
//
//The original authors reserve the right to license this program or
//modified versions of this program under other licenses at our
//discretion.
//
//If you publish results generated by this software please cite
//Lisiecki, L. E. and P. A. Lisiecki, The application of dynamic
//  programming to the correlation of paleoclimate records, 
//  submitted to Paleoceanography, 2001
//
//Any questions regarding this license or the operation of this
//software may be directed to Lorraine Lisiecki
//<zogalum@alum.mit.edu>.

#ifndef MATCH_SERIES
#define MATCH_SERIES

#include <stdio.h>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <exception>
#include <stdexcept>
#include "assert.h"

namespace std {}
using namespace std;

class format_error: public runtime_error {
  //  string why;
public:
  format_error(string why): runtime_error(why) {}
  //  virtual const char *what() throw() {
  //    return why.c_str();
  //  }
};

string as_string(int i);
string as_string(unsigned int i);
string as_string(float f);

struct point{
  float x, y;
  inline point(float x, float y):x(x),y(y){}
  point(){}
};

static float intervalmap(float i1b, float i1e, float i2b, 
			 float i2e, float x1) {
  return(i2b+((i2e-i2b)*(x1-i1b)/(i1e-i1b)));
}

static int xless(const void *p1,const void *p2) {
  const point *pt1=(const point *)p1;
  const point *pt2=(const point *)p2;
  if(pt1->x == pt2->x) {
    if(pt1->y == pt2->y) {
      return 0;
    }
    return (pt1->y < pt2->y)?-1:1;
  }    
  return (pt1->x < pt2->x)?-1:1;
}



// Warning: Data memory is not freed when a core is destroyed...
struct core {
  int label;
  int size;
  point *data;
  point &operator[](int i){
    return data[i];
  }
  const point &operator[](int i) const{
    return data[i];
  }
  struct division{
    float start, interval;
    int count;
    int *indices;
    int *findindices (core &c) {
      int *result=new int[count+1];
      int di, ci;
      for(di=0, ci=0; di<=count; di++) {
	float x=beginval(di);
	while(ci<=c.size-1 && c[ci].x<x) ci++;
	result[di]=ci;
      }
      return(result);
    }
    void setdivision(float start, float interval, int count, core &c) {
      this->start=start;
      this->interval=interval;
      this->count=count;
      delete [] indices;
      indices=findindices(c);
    }
    division() :
      start(0), interval(1), count(1), indices(new int[2]()) {}
    float beginval(int i) const {
      return(start+i*interval);
    }
    float endval() const {
      return(start+count*interval);
    }
  };

  division div;
  void setdivision(float start, float interval, int count) {
    div.setdivision(start,interval,count,*this);
  }

  float interp(float x, int &hint, bool &inrange) const{
    assert(size>=2);
    if(hint>=size-1) hint=size-2;
    if(hint<0) hint=0;
    while(hint>=0 && data[hint].x>x) hint--;
    if(hint<0) {
      inrange=false;
      return data[0].y;
    }
    while(hint<=size-2 && data[hint+1].x<=x) hint++;
    if(hint>=size-1) {
      inrange=false;
      return data[size-1].y;
    }
    inrange=true;
    return data[hint].y+((data[hint+1].y-data[hint].y)*(x-data[hint].x)
			 /(data[hint+1].x-data[hint].x));
  }

  void add (const point &p){
    data=static_cast<point*>(realloc(data,sizeof(point)*++size));
    assert(data);
    data[size-1]=p;
  }

  void insert(const point &p, int i){
    data=static_cast<point*>(realloc(data,sizeof(point)*++size));
    assert(data);
    memmove(&data[i+1], &data[i], sizeof(point)*(size-i-1));
    data[i]=p;
  }    

  void erase(int i) {
    memmove(&data[i], &data[i+1], sizeof(point)*(size-i-1));
    size--;
  }

  // Split this core at point i, returning rest in new core
  core* split(int i) {
    core* result=new core;
    for(int j=i; j<size; j++) 
      result->add(data[j]);
    size=i;
    return result;
  }

  core transpose() {
    core result;
    for(int i=0; i<size;i++) {
      result.add(point(data[i].y,data[i].x));
    }
    return result;
  }

  void sort() {
    qsort(data,size,sizeof(data[0]),xless);
  }

  bool sorted() {
    if(size<2) return true;
    for(int i=0; i < size-1; i++) {
      if(data[i].x > data[i+1].x) {
	return false;
      }
    }
    return true;
  }

  // This function takes the average y-value of consecutive points
  // with the same x-value and returns the number of points removed
  // from the series.
  int averageduplicates() {
    int originalsize=size;
    for(int i=0;i<size;i++) {
      float x=data[i].x;
      int j;
      for(j=i+1;j<size && data[j].x==x; j++) {
	data[i].y += data[j].y;
      }
      if(j-i > 1) {
	data[i].y /= j-i;
	memmove(&data[i+1],&data[j],sizeof(point)*(size-j));
	size -= (j-i)-1;
      }
    }
    return (originalsize-size);
  }

  core(int newlabel=0) {
    label=newlabel;
    size=0;
    data=NULL;
  }
};

struct series {
  // Allows multiple signals for each series
  // cores[corenum][signalnum]
  vector<vector<core*> > cores;
  float start, interval;
  int count;

  // Maps the user's core label to the program's first corenum 
  // for that label
  typedef map<int,int> labelmap_t;
  labelmap_t labelmap;
  void build_labelmap() throw(format_error) {
    labelmap.clear();
    int prev_label=0;
    for(unsigned int i=0; i<cores.size(); i++) {
      if(!(labelmap.insert(make_pair(cores[i][0]->label, i)).second)) {
	if(prev_label != cores[i][0]->label) {
	  throw format_error("Series contains duplicate core label "+
			     as_string(cores[i][0]->label));
	}
      }
      prev_label=cores[i][0]->label;
    }
  }

  int lookup_label(int label, float depth) throw(format_error) {
    labelmap_t::iterator i=labelmap.find(label);
    if(i==labelmap.end())
      throw format_error("Unknown core label "+as_string(label));
    int ci=i->second;
    for(;ci<int(cores.size()) && cores[ci][0]->label==label; ci++)
      if(depth>=cores[ci][0]->div.beginval(0) && 
	 depth<cores[ci][0]->div.endval())
	return ci;
    throw format_error("Tie point or gap depth "+ as_string(depth)+
		       "not associated with core label "+
		       as_string(label));
  }
};

series merge_signal_series(vector<series> &sv);
series readfile(string name);
bool writefile(ostream &out,series &s, int signalnum);
bool writefile(string name,series &s, int signalnum);

struct correspondence {
  int core1num, core2num;
  int int1, int2;
  int speed;

  correspondence(int core1num, int int1, int core2num, int int2, int speed) {
    this->core1num=core1num;
    this->core2num=core2num;
    this->int1=int1;
    this->int2=int2;
    this->speed=speed;
  }

  correspondence() {
    this->core1num=-1;
    this->core2num=-1;
    this->int1=-1;
    this->int2=-1;
    this->speed=-1;
  }

  correspondence transpose() const{
    return correspondence(core2num, int2, core1num, int1, speed);
  }

};

struct path: public vector<correspondence> {
  path transpose() const {
    path result=*this;
    for(path::iterator i=result.begin();
	i!=result.end(); i++) 
      *i=i->transpose();
    return result;
  }

};

#if 0
series newx(series &xmatch, series &s) {
  int hint=0;
  series news;
  if(xmatch.size==0){
    return news;
  }
  for(int i=0;i<s.size;i++) {
    point p=s[i];
    if(p.x>=xmatch[0].x && p.x<=xmatch[xmatch.size-1].x) {
      p.x=xmatch.interp(p.x,hint);
      news.add(p);
    }
  }
  return news;
}

series subseries(series &s, float beg, float end) {
  series news;
  for(int i=0;i<s.size;i++) {
    point &p=s[i];
    if(p.x>=beg && p.x<=end){
      news.add(p);
    }
  }
  return news;
}
#endif

struct tiept {
  int core1num, core2num;
  float depth1, depth2;
  float weight;

  tiept(int core1num, float depth1, int core2num, float depth2, float weight) {
    this->core1num=core1num;
    this->core2num=core2num;
    this->depth1=depth1;
    this->depth2=depth2;
    this->weight=weight;
  }

  tiept() {
    this->core1num=-1;
    this->core2num=-1;
    this->depth1=-1;
    this->depth2=-1;
    this->weight=-1;
  }

  tiept transpose() const{
    return tiept(core2num, depth2, core1num, depth1, weight);
  }
};

struct tiepts{
  vector<tiept> raw;

  // Warning: Not all memory is freed
  struct corepairties {
    vector<tiept> ties;
    // s1ties_left = # of ties between current core in s1 
    // and end of s1 (inclusive) that tie to the
    // current core in s2
    int s1ties_left, s2ties_left;
    
    corepairties() {
      s1ties_left=0;
      s2ties_left=0;
    }

    int *find_indices(const core &c) const {
      int *indices=new int[c.div.count+1];
      int di;
      unsigned int ci;
      for(di=0, ci=0; di<=c.div.count; di++) {
	float x=c.div.beginval(di);
	while(ci+1 <= ties.size() && ties[ci].depth1<x) ci++;
	indices[di]=ci;
      }
      return(indices);
    }
  };

  class tiemap_t: public map<int,map<int,corepairties > > {
  public:
    const corepairties &lookup(int core1num, int core2num) const {
      static corepairties empty;
      tiemap_t::const_iterator i=find(core1num);
      if(i==end()) return empty;
      tiemap_t::mapped_type::const_iterator ii=i->second.find(core2num);
      if(ii==i->second.end()) return empty;    
      return ii->second;
    }
  };

  tiemap_t c1_c2_tie;
  tiemap_t c2_c1_tieT;

  void addtiept(vector<tiept> &tiemap, tiept &t) throw(format_error) {
    for(vector<tiept>::iterator i=tiemap.begin(); 
	i!=tiemap.end(); 
	i++) {
      if(t.depth1 < i->depth1) {
	if(t.depth2 < i->depth2) {
	  tiemap.insert(i,t);
	  return;
	} else {
	  throw format_error("Error: Crossed tie points");
	} 
      }
      //      if(t.depth1==i->depth1) {
      //	throw format_error("Error: Duplicate depth in tie points");
      //      }
      //      if(t.depth2 >= i->depth2) {
      //	throw format_error("Error: Crossed tie points");
      //      } 
    }
    tiemap.push_back(t);
  }


  void buildmaps(series &s1, series &s2) throw(format_error) {
    // Sort tie points by core numbers and depth
    for(unsigned int i=0; i<raw.size(); i++) {
      tiept t=raw[i];
      try {
	t.core1num=s1.lookup_label(t.core1num,t.depth1);
      } catch (format_error &e) { 
	throw format_error(string(e.what())+" in series 1");
      }
      try {
	t.core2num=s2.lookup_label(t.core2num,t.depth2);
      } catch (format_error &e) { 
	throw format_error(string(e.what())+" in series 2");
      }
      addtiept(c1_c2_tie[t.core1num][t.core2num].ties,t);
      t=t.transpose();
      addtiept(c2_c1_tieT[t.core1num][t.core2num].ties,t);
    }

    // Compute s1ties_left and s2ties_left
    for(int c1=s1.cores.size()-1; c1>=0; c1--) {
      int n=0;
      for(int c2=s2.cores.size()-1; c2>=0; c2--) {
	n+=c1_c2_tie[c1][c2].ties.size();
	c1_c2_tie[c1][c2].s2ties_left=n;
	c2_c1_tieT[c2][c1].s1ties_left=n;
      }
    }
    for(int c2=s2.cores.size()-1; c2>=0; c2--) {
      int n=0;
      for(int c1=s1.cores.size()-1; c1>=0; c1--) {
	n+=c1_c2_tie[c1][c2].ties.size();
	c1_c2_tie[c1][c2].s1ties_left=n;
	c2_c1_tieT[c2][c1].s2ties_left=n;
      }
    }
  }

  
};

tiepts read_tiefile(string name, ostream &log) throw(format_error);

struct gap{
  int corenum;
  float start, end;
  bool startnan, endnan;
  gap(int corenum, float start, bool startnan, float end, bool endnan):
    corenum(corenum), start(start), end(end), startnan(startnan), 
    endnan(endnan){}
};

struct gaps{
  vector<gap> raw;

  void apply_gaps(series &s) throw(format_error) {
    for(unsigned int i=0; i<raw.size(); i++) {
      gap &g=raw[i];
      bool modified=false;
      for(unsigned int j=0; j<s.cores.size();j++) {
	vector<core*> &cv=s.cores[j];
	if(cv[0]->label == g.corenum) {
	  bool before=false;  //found points before gap
	  bool after=false;
	  vector<core*> beforecv, aftercv;
	  for(unsigned int sig=0; sig<cv.size(); sig++) {
	    beforecv.push_back(new core(cv[sig]->label));
	    aftercv.push_back(new core(cv[sig]->label));
	    core &c=*(cv[sig]);
	    for(int k=0; k<c.size; k++) {
	      if(!g.startnan && c[k].x < g.start) {
		before=true;
		beforecv.back()->add(c[k]);
	      } else if(!g.endnan && c[k].x >= g.end) {
		after=true;
		aftercv.back()->add(c[k]);
	      } else {
		modified=true;
	      }
	    }
	    delete cv[sig];
	  }
	  if(before && after) {
	    s.cores[j]=beforecv;
	    s.cores.insert(s.cores.begin()+j+1,aftercv);
	    modified=true;
	    j+=2;
	  } else if(before) {
	    s.cores[j]=beforecv;
	    j++;
	  } else if(after) {
	    s.cores[j]=aftercv;
	    j++;
	  } else {
	    s.cores.erase(s.cores.begin()+j);
	  }
	  // Memory leak: empty beforecv/aftercv cores
	}
      }
      if(!modified) {
	throw format_error("Gap "+as_string(i+1)+" does not alter series");
      }
    }
  }	  
};

gaps read_gapfile(string name) throw(format_error);

#endif // #ifndef MATCH_SERIES
