// **************************************************************************
//
//    PARALUTION   www.paralution.com
//
//    Copyright (C) 2015  PARALUTION Labs UG (haftungsbeschränkt) & Co. KG
//                        Am Hasensprung 6, 76571 Gaggenau
//                        Handelsregister: Amtsgericht Mannheim, HRA 706051
//                        Vertreten durch:
//                        PARALUTION Labs Verwaltungs UG (haftungsbeschränkt)
//                        Am Hasensprung 6, 76571 Gaggenau
//                        Handelsregister: Amtsgericht Mannheim, HRB 721277
//                        Geschäftsführer: Dimitar Lukarski, Nico Trost
//
//    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, either version 3 of the License, or
//    (at your option) any later version.
//
//    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, see <http://www.gnu.org/licenses/>.
//
// **************************************************************************



// PARALUTION version 1.1.0 


#include "../../utils/def.hpp"
#include "mic_vector.hpp"
#include "../base_vector.hpp"
#include "../host/host_vector.hpp"
#include "../backend_manager.hpp"
#include "../../utils/log.hpp"
#include "../../utils/allocate_free.hpp"
#include "mic_utils.hpp"
#include "mic_allocate_free.hpp"
#include "mic_vector_kernel.hpp"



namespace paralution {

template <typename ValueType>
MICAcceleratorVector<ValueType>::MICAcceleratorVector() {

  // no default constructors
  LOG_INFO("no default constructor");
  FATAL_ERROR(__FILE__, __LINE__);

}

template <typename ValueType>
MICAcceleratorVector<ValueType>::MICAcceleratorVector(const Paralution_Backend_Descriptor local_backend) {

  LOG_DEBUG(this, "MICAcceleratorVector::MICAcceleratorVector()",
            "constructor with local_backend");

  this->vec_ = NULL;
  this->set_backend(local_backend); 

}


template <typename ValueType>
MICAcceleratorVector<ValueType>::~MICAcceleratorVector() {

  LOG_DEBUG(this, "MICAcceleratorVector::~MICAcceleratorVector()",
            "destructor");

  this->Clear();

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::info(void) const {

  LOG_INFO("MICAcceleratorVector<ValueType>");

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Allocate(const int n) {

  assert(n >= 0);

  if (this->get_size() >0)
    this->Clear();

  if (n > 0) {

    allocate_mic(this->local_backend_.MIC_dev,
		 n, &this->vec_);
    set_to_zero_mic(this->local_backend_.MIC_dev,
		    n, this->vec_);
    this->size_ = n;
  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::SetDataPtr(ValueType **ptr, const int size) {

  assert(*ptr != NULL);
  assert(size > 0);

  this->vec_ = *ptr;
  this->size_ = size;

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::LeaveDataPtr(ValueType **ptr) {

  assert(this->get_size() > 0);

  *ptr = this->vec_;
  this->vec_ = NULL;

  this->size_ = 0 ;

}


template <typename ValueType>
void MICAcceleratorVector<ValueType>::Clear(void) {
  
  if (this->get_size() >0) {

    free_mic(this->local_backend_.MIC_dev,
	     &this->vec_);

    this->size_ = 0 ;

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyFromHost(const HostVector<ValueType> &src) {

  // CPU to MIC copy
  const HostVector<ValueType> *cast_vec;
  if ((cast_vec = dynamic_cast<const HostVector<ValueType>*> (&src)) != NULL) {

  if (this->get_size() == 0)
    this->Allocate(cast_vec->get_size());
    
    assert(cast_vec->get_size() == this->get_size());

    if (this->get_size() >0) {

      copy_to_mic(this->local_backend_.MIC_dev,
		  cast_vec->vec_, this->vec_, this->get_size());

    }

  } else {
    
    LOG_INFO("Error unsupported MIC vector type");
    this->info();
    src.info();
    FATAL_ERROR(__FILE__, __LINE__);
    
  }

}



template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyToHost(HostVector<ValueType> *dst) const {

  // MIC to CPU copy
  HostVector<ValueType> *cast_vec;
  if ((cast_vec = dynamic_cast<HostVector<ValueType>*> (dst)) != NULL) {

  if (cast_vec->get_size() == 0)
    cast_vec->Allocate(this->get_size());  
    
    assert(cast_vec->get_size() == this->get_size());

    if (this->get_size() >0) {

      copy_to_host(this->local_backend_.MIC_dev,
		   this->vec_, cast_vec->vec_, this->get_size());

    }

  } else {
    
    LOG_INFO("Error unsupported MIC vector type");
    this->info();
    dst->info();
    FATAL_ERROR(__FILE__, __LINE__);
    
  }

  
}


template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyFrom(const BaseVector<ValueType> &src) {

  const MICAcceleratorVector<ValueType> *mic_cast_vec;
  const HostVector<ValueType> *host_cast_vec;


    // MIC to MIC copy
    if ((mic_cast_vec = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&src)) != NULL) {

      if (this->get_size() == 0)
        this->Allocate(mic_cast_vec->get_size());

      assert(mic_cast_vec->get_size() == this->get_size());

      if (this != mic_cast_vec)  {  

        if (this->get_size() >0) {

	  copy_mic_mic(this->local_backend_.MIC_dev,
		       mic_cast_vec->vec_, this->vec_, this->get_size());

        }

      }

    } else {
      
      //MIC to CPU copy
      if ((host_cast_vec = dynamic_cast<const HostVector<ValueType>*> (&src)) != NULL) {
        

        this->CopyFromHost(*host_cast_vec);
        
      
      } else {

        LOG_INFO("Error unsupported MIC vector type");
        this->info();
        src.info();
        FATAL_ERROR(__FILE__, __LINE__);
        
      }
      
    }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyFrom(const BaseVector<ValueType> &src,
                                               const int src_offset,
                                               const int dst_offset,
                                               const int size) {

  assert(&src != this);
  assert(this->get_size() > 0);
  assert(src.  get_size() > 0);
  assert(size > 0);

  assert(src_offset + size <= src.get_size());
  assert(dst_offset + size <= this->get_size());

  const MICAcceleratorVector<ValueType> *cast_src = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&src);
  assert(cast_src != NULL);

  
  copy_mic_mic(this->local_backend_.MIC_dev,
	       cast_src->vec_+src_offset, 
	       this->vec_+dst_offset, 
	       size);
  
}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyTo(BaseVector<ValueType> *dst) const{

  MICAcceleratorVector<ValueType> *mic_cast_vec;
  HostVector<ValueType> *host_cast_vec;

    // MIC to MIC copy
    if ((mic_cast_vec = dynamic_cast<MICAcceleratorVector<ValueType>*> (dst)) != NULL) {

      if (mic_cast_vec->get_size() == 0)
        mic_cast_vec->Allocate(this->get_size());

      assert(mic_cast_vec->get_size() == this->get_size());

      if (this != mic_cast_vec)  {  

        if (this->get_size() >0) {

	  copy_mic_mic(this->local_backend_.MIC_dev,
		       this->vec_, mic_cast_vec->vec_, this->get_size());

        }
      }

    } else {
      
      //MIC to CPU copy
      if ((host_cast_vec = dynamic_cast<HostVector<ValueType>*> (dst)) != NULL) {
        

        this->CopyToHost(host_cast_vec);
        
      
      } else {

        LOG_INFO("Error unsupported MIC vector type");
        this->info();
        dst->info();
        FATAL_ERROR(__FILE__, __LINE__);
        
      }
      
    }


}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Zeros(void) {

  if (this->get_size() > 0) {

    set_to_zero_mic(this->local_backend_.MIC_dev,
		    this->get_size(), this->vec_);
    
  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Ones(void) {

  if (this->get_size() > 0)
    set_to_one_mic(this->local_backend_.MIC_dev,
		   this->get_size(), this->vec_);

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::SetValues(const ValueType val) {

  LOG_INFO("MICAcceleratorVector::SetValues NYI");
  FATAL_ERROR(__FILE__, __LINE__);

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::AddScale(const BaseVector<ValueType> &x, const ValueType alpha) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    assert(cast_x != NULL);

    addscale(this->local_backend_.MIC_dev,
	     cast_x->vec_, alpha, this->get_size(), this->vec_);

  }

}


template <typename ValueType>
void MICAcceleratorVector<ValueType>::ScaleAdd(const ValueType alpha, const BaseVector<ValueType> &x) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    assert(cast_x != NULL);

    scaleadd(this->local_backend_.MIC_dev,
	     cast_x->vec_, alpha, this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::ScaleAddScale(const ValueType alpha, const BaseVector<ValueType> &x, const ValueType beta) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    assert(cast_x != NULL);

    scaleaddscale(this->local_backend_.MIC_dev,
		  cast_x->vec_, alpha, beta, this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::ScaleAddScale(const ValueType alpha, const BaseVector<ValueType> &x, const ValueType beta,
                                          const int src_offset, const int dst_offset,const int size) {

  assert(this->get_size() > 0);
  assert(x.    get_size() > 0);
  assert(size > 0);

  assert(src_offset + size <= x.get_size());
  assert(dst_offset + size <= this->get_size());

  const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
  assert(cast_x != NULL);

  scaleaddscale(this->local_backend_.MIC_dev,
		cast_x->vec_, alpha, beta, this->vec_,
		src_offset, dst_offset, size);



}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::ScaleAdd2(const ValueType alpha, const BaseVector<ValueType> &x, const ValueType beta, const BaseVector<ValueType> &y, const ValueType gamma) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    assert(this->get_size() == y.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    const MICAcceleratorVector<ValueType> *cast_y = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&y);
    assert(cast_x != NULL);
    assert(cast_y != NULL);

    scaleadd2(this->local_backend_.MIC_dev,
	      cast_x->vec_, cast_y->vec_,
	      alpha, beta, gamma,
	      this->get_size(),
	      this->vec_);
    
  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Scale(const ValueType alpha) {

  if (this->get_size() > 0) {

    scale(this->local_backend_.MIC_dev,
	  alpha, this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::ExclusiveScan(const BaseVector<ValueType> &x) {

  LOG_INFO("MICAcceleratorVector::ExclusiveScan() NYI");
  FATAL_ERROR(__FILE__, __LINE__); 

}

template <typename ValueType>
ValueType MICAcceleratorVector<ValueType>::Dot(const BaseVector<ValueType> &x) const {

  assert(this->get_size() == x.get_size());

  const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
  assert(cast_x != NULL);

  ValueType d;

  dot(this->local_backend_.MIC_dev,
      this->vec_, cast_x->vec_, this->get_size(), d);

  return d;
}

template <typename ValueType>
ValueType MICAcceleratorVector<ValueType>::DotNonConj(const BaseVector<ValueType> &x) const {

  return this->Dot(x);
}


template <typename ValueType>
ValueType MICAcceleratorVector<ValueType>::Asum(void) const {

  ValueType asumv;

  asum(this->local_backend_.MIC_dev,
       this->vec_, this->get_size(), asumv);

  return asumv;

}

template <typename ValueType>
int MICAcceleratorVector<ValueType>::Amax(ValueType &value) const {

  int index = 0;

  amax(this->local_backend_.MIC_dev,
       this->vec_, this->get_size(), value, index);

  return index;

}


template <typename ValueType>
ValueType MICAcceleratorVector<ValueType>::Norm(void) const {

  ValueType n;

  norm(this->local_backend_.MIC_dev,
       this->vec_, this->get_size(), n);

  return n;

}


template <typename ValueType>
ValueType MICAcceleratorVector<ValueType>::Reduce(void) const {

  ValueType r = ValueType(0.0);

  reduce(this->local_backend_.MIC_dev,
	 this->vec_, this->get_size(), r);

  return r;

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::PointWiseMult(const BaseVector<ValueType> &x) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    assert(cast_x != NULL);

    pointwisemult(this->local_backend_.MIC_dev,
		  cast_x->vec_, this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::PointWiseMult(const BaseVector<ValueType> &x, const BaseVector<ValueType> &y) {

  if (this->get_size() > 0) {

    assert(this->get_size() == x.get_size());
    assert(this->get_size() == y.get_size());
    
    const MICAcceleratorVector<ValueType> *cast_x = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&x);
    const MICAcceleratorVector<ValueType> *cast_y = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&y);
    assert(cast_x != NULL);
    assert(cast_y != NULL);

    pointwisemult2(this->local_backend_.MIC_dev,
		   cast_x->vec_, cast_y->vec_, this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Permute(const BaseVector<int> &permutation) {

  if (this->get_size() > 0) {

    assert(&permutation != NULL);
    assert(this->get_size() == permutation.get_size());
    
    const MICAcceleratorVector<int> *cast_perm = dynamic_cast<const MICAcceleratorVector<int>*> (&permutation);
    assert(cast_perm != NULL);
    
    MICAcceleratorVector<ValueType> vec_tmp(this->local_backend_);     
    vec_tmp.Allocate(this->get_size());
    vec_tmp.CopyFrom(*this);

    permute(this->local_backend_.MIC_dev,
	    cast_perm->vec_, vec_tmp.vec_,
	    this->get_size(), this->vec_);

  }
}


template <typename ValueType>
void MICAcceleratorVector<ValueType>::PermuteBackward(const BaseVector<int> &permutation) {

  if (this->get_size() > 0) {

    assert(&permutation != NULL);
    assert(this->get_size() == permutation.get_size());
    
    const MICAcceleratorVector<int> *cast_perm = dynamic_cast<const MICAcceleratorVector<int>*> (&permutation);
    assert(cast_perm != NULL);
    
    MICAcceleratorVector<ValueType> vec_tmp(this->local_backend_);   
    vec_tmp.Allocate(this->get_size());
    vec_tmp.CopyFrom(*this);

    permuteback(this->local_backend_.MIC_dev,
		cast_perm->vec_, vec_tmp.vec_,
		this->get_size(), this->vec_);

  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyFromPermute(const BaseVector<ValueType> &src,
                                                      const BaseVector<int> &permutation) { 

  if (this->get_size() > 0) {

    assert(this != &src);
    
    const MICAcceleratorVector<ValueType> *cast_vec = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&src);
    const MICAcceleratorVector<int> *cast_perm      = dynamic_cast<const MICAcceleratorVector<int>*> (&permutation) ; 
    assert(cast_perm != NULL);
    assert(cast_vec  != NULL);
    
    assert(cast_vec ->get_size() == this->get_size());
    assert(cast_perm->get_size() == this->get_size());

    permute(this->local_backend_.MIC_dev,
	    cast_perm->vec_, cast_vec->vec_,
	    this->get_size(),
	    this->vec_);
 
  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::CopyFromPermuteBackward(const BaseVector<ValueType> &src,
                                                              const BaseVector<int> &permutation) {

  if (this->get_size() > 0) {

    assert(this != &src);
    
    const MICAcceleratorVector<ValueType> *cast_vec = dynamic_cast<const MICAcceleratorVector<ValueType>*> (&src);
    const MICAcceleratorVector<int> *cast_perm      = dynamic_cast<const MICAcceleratorVector<int>*> (&permutation) ; 
    assert(cast_perm != NULL);
    assert(cast_vec  != NULL);
    
    assert(cast_vec ->get_size() == this->get_size());
    assert(cast_perm->get_size() == this->get_size());

    permuteback(this->local_backend_.MIC_dev,
		cast_perm->vec_, cast_vec->vec_,
		this->get_size(),
		this->vec_);
        
  }

}

template <typename ValueType>
void MICAcceleratorVector<ValueType>::Power(const double val) {

  if (this->get_size() > 0) {
    
    power(this->local_backend_.MIC_dev,
          this->get_size(), val, this->vec_);
  
  }

}


template class MICAcceleratorVector<double>;
template class MICAcceleratorVector<float>;

template class MICAcceleratorVector<int>;

}