#include <vector>

#include "caffe/blob.hpp"
#include "caffe/common.hpp"
#include "caffe/filler.hpp"
#include "caffe/layer.hpp"
#include "caffe/util/math_functions.hpp"
#include "caffe/vision_layers.hpp"

namespace caffe {

template <typename Dtype>
void InnerProductLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      vector<Blob<Dtype>*>* top) {
  const int num_output = this->layer_param_.inner_product_param().num_output();
  bias_term_ = this->layer_param_.inner_product_param().bias_term();
  N_ = num_output;
  K_ = bottom[0]->count() / bottom[0]->num();
  // Check if we need to set up the weights
  if (this->blobs_.size() > 0) {
    LOG(INFO) << "Skipping parameter initialization";
  } else {
    if (bias_term_) {
      this->blobs_.resize(2);
    } else {
      this->blobs_.resize(1);
    }
    // Intialize the weight
    this->blobs_[0].reset(new Blob<Dtype>(1, 1, N_, K_));
    // fill the weights
    shared_ptr<Filler<Dtype> > weight_filler(GetFiller<Dtype>(
        this->layer_param_.inner_product_param().weight_filler()));
    weight_filler->Fill(this->blobs_[0].get());
    // If necessary, intiialize and fill the bias term
    if (bias_term_) {
      this->blobs_[1].reset(new Blob<Dtype>(1, 1, 1, N_));
      shared_ptr<Filler<Dtype> > bias_filler(GetFiller<Dtype>(
          this->layer_param_.inner_product_param().bias_filler()));
      bias_filler->Fill(this->blobs_[1].get());
    }
  }  // parameter initialization
  this->param_propagate_down_.resize(this->blobs_.size(), true);
}

template <typename Dtype>
void InnerProductLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      vector<Blob<Dtype>*>* top) {
  // Figure out the dimensions
  M_ = bottom[0]->num();
  CHECK_EQ(bottom[0]->count() / bottom[0]->num(), K_) << "Input size "
    "incompatible with inner product parameters.";
  for (int i=0;i<top->size();i++) {
	  (*top)[i]->Reshape(bottom[i]->num(), N_, 1, 1);
  }
  //(*top)[0]->Reshape(bottom[0]->num(), N_, 1, 1);
  // Set up the bias multiplier
  if (bias_term_) {
    bias_multiplier_.Reshape(1, 1, 1, M_);
    caffe_set(M_, Dtype(1), bias_multiplier_.mutable_cpu_data());
  }
}

template <typename Dtype>
void InnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    vector<Blob<Dtype>*>* top) {
	if (this->layer_param_.has_unuse()) {
			//bool layer_unuse = this->layer_param_.unuse();
			if (this->layer_param_.unuse()) {return;}
	}
	//LOG(INFO) << "num:" << bottom[0]->num() << ", dim:" << K_ << ", output dim:" << N_;
	const Dtype* weight = this->blobs_[0]->cpu_data();
	for (int i=0;i<bottom.size();i++) {
		const Dtype* bottom_data = bottom[i]->cpu_data();
		Dtype* top_data = (*top)[i]->mutable_cpu_data();
		M_ = bottom[i]->num();
		caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1.,
		      bottom_data, weight, (Dtype)0., top_data);
		if (bias_term_) {
		    caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1.,
		        bias_multiplier_.cpu_data(),
		        this->blobs_[1]->cpu_data(), (Dtype)1., top_data);
		  }
		/*if (this->layer_param_.has_recording()) {
		  if (this->layer_param_.recording()) {
			  std::ofstream file;
			    file.open("./loss_ip.txt", ios::out|ios::app);
			    int dim = bottom[i]->count() / bottom[i]->num();
			      for (int k = 0; k < bottom[i]->num(); ++k)
			      {
			    	  file << k << "\t";
			    	  Dtype * thisdata = bottom_data + bottom[i]->offset(k);
			    	  for (int j=0; j<dim; ++j)
			    	  {
			    		  file << bottom_data[k*dim+j] << "\t";
			    	  }
			    	  file << "\n";
			      }
			      file.close();
		  }
		  }*/
		/*if (N_==500||((N_==1000)&&(bottom.size()==1)))
		{
		std::ofstream file;
					    file.open("./loss_fc.txt", ios::out|ios::app);
					    int dim = (*top)[i]->count() / (*top)[i]->num();
					      for (int k = 0; k < (*top)[i]->num(); ++k)
					      {
					    	  file << this->layer_param_.name() << "\t" << i << "\t" << k << "\t";
					    	  Dtype * thisdata = top_data + (*top)[i]->offset(k);
					    	  for (int j=0; j<dim; ++j)
					    	  {
					    		  file << thisdata[j] << "\t";
					    	  }
					    	  file << "\n";
					      }
					      file.close();
		}*/
	}
  const Dtype* bottom_data = bottom[0]->cpu_data();
  Dtype* top_data = (*top)[0]->mutable_cpu_data();

  /*caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasTrans, M_, N_, K_, (Dtype)1.,
      bottom_data, weight, (Dtype)0., top_data);*/
  /*if (bias_term_) {
    caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1.,
        bias_multiplier_.cpu_data(),
        this->blobs_[1]->cpu_data(), (Dtype)1., top_data);
  }*/
  if (this->layer_param_.has_recording()) {
  if (this->layer_param_.recording()) {
	  std::ofstream file;
	    file.open("./loss_ip.txt", ios::out|ios::app);
	    int dim = bottom[0]->count() / bottom[0]->num();
	      for (int i = 0; i < bottom[0]->num(); ++i)
	      {
	    	  file << i << "\t";
	    	  for (int j=0; j<dim; ++j)
	    	  {
	    		  file << bottom_data[i*dim+j] << "\t";
	    	  }
	    	  file << "\n";
	      }
	      file.close();
  }
  }
}

template <typename Dtype>
void InnerProductLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down,
    vector<Blob<Dtype>*>* bottom) {
	if (this->layer_param_.has_unuse()) {
			//bool layer_unuse = this->layer_param_.unuse();
			if (this->layer_param_.unuse()) {return;}
		}
  if (this->param_propagate_down_[0]) {
	  for (int i=0;i<top.size();i++) {
		  const Dtype* top_diff = top[i]->cpu_diff();
		  const Dtype* bottom_data = (*bottom)[i]->cpu_data();
		  M_ = (*bottom)[i]->num();
		  if (i==0) {
			  caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,
			  		          top_diff, bottom_data, (Dtype)0., this->blobs_[0]->mutable_cpu_diff());
		  } else {
			  caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,
			  		          top_diff, bottom_data, (Dtype)1., this->blobs_[0]->mutable_cpu_diff());
		  }
	  }
    //const Dtype* top_diff = top[0]->cpu_diff();
    //const Dtype* bottom_data = (*bottom)[0]->cpu_data();
    // Gradient with respect to weight
    /*caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,
        top_diff, bottom_data, (Dtype)0., this->blobs_[0]->mutable_cpu_diff());*/
  }
  if (bias_term_ && this->param_propagate_down_[1]) {
	  for (int i=0;i<top.size();i++) {
		  const Dtype* top_diff = top[i]->cpu_diff();
		  M_ = (*bottom)[i]->num();
		  if (i==0) {
			  caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,
			  		          bias_multiplier_.cpu_data(), (Dtype)0.,
			  		          this->blobs_[1]->mutable_cpu_diff());
		  } else {
			  caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,
			  		          bias_multiplier_.cpu_data(), (Dtype)1.,
			  		          this->blobs_[1]->mutable_cpu_diff());
		  }
	  }
    //const Dtype* top_diff = top[0]->cpu_diff();
    // Gradient with respect to bias
    /*caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,
        bias_multiplier_.cpu_data(), (Dtype)0.,
        this->blobs_[1]->mutable_cpu_diff());*/
  }
  if (propagate_down[0]) {
	  for (int i=0;i<top.size();i++) {
		  const Dtype* top_diff = top[i]->cpu_diff();
		  M_ = (*bottom)[i]->num();
		  caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,
			  		          top_diff, this->blobs_[0]->cpu_data(), (Dtype)0.,
			  		          (*bottom)[i]->mutable_cpu_diff());
	  }
    //const Dtype* top_diff = top[0]->cpu_diff();
    // Gradient with respect to bottom data
    /*caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,
        top_diff, this->blobs_[0]->cpu_data(), (Dtype)0.,
        (*bottom)[0]->mutable_cpu_diff());*/
  }
  if (this->layer_param_.has_diff_threshold()) {
  	  Dtype diff_threshold = Dtype(this->layer_param_.diff_threshold());
  	  Dtype * weight_diff = this->blobs_[0]->mutable_cpu_diff();
  	  for (int i=0; i<this->blobs_[0]->count(); i++) {
  		  if (weight_diff[i] > diff_threshold) {weight_diff[i] = diff_threshold;}
  		  if (weight_diff[i] < -diff_threshold) {weight_diff[i] = -diff_threshold;}
  	  }
  	  if (bias_term_) {
  		  Dtype * bias_diff = this->blobs_[1]->mutable_cpu_diff();
  		for (int i=0; i<this->blobs_[1]->count(); i++) {
  			if (bias_diff[i] > diff_threshold) {bias_diff[i] = diff_threshold;}
  			if (bias_diff[i] < -diff_threshold) {bias_diff[i] = -diff_threshold;}
  		}
  	  }
  }
}

#ifdef CPU_ONLY
STUB_GPU(InnerProductLayer);
#endif

INSTANTIATE_CLASS(InnerProductLayer);

}  // namespace caffe
