/* This file is part of the source code for the following publication:
 * Assembling Self-Supporting Structures, Deuss et al., SIGGRAPH Asia 2014
 *
 * Copyright (C) 2014 Mario Deuss <mario.deuss@epfl.ch>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef SEQUENCE_H
#define SEQUENCE_H

#include <algorithm>
#include "SelfAssemblyDefines.h"
#include "Statistics.h"

namespace SelfAssembly{
class Assembly;

class State{
public:
	struct ChainIdxs { Idx ai_; Idx bi_; ChainIdxs(Idx ai, Idx bi):ai_(ai),bi_(bi){;}};
	struct ChainIdxsComparison {
		ChainIdxsComparison( const ChainIdxs& ci ) : ci_(ci) {}
		bool operator()( const ChainIdxs& cj ) const { return (( cj.ai_ == ci_.ai_) && ( cj.bi_ == ci_.bi_ )) ; }
		private: const ChainIdxs& ci_;
	};

	typedef std::vector<ChainIdxs> ChainsIdxs;

	Idx addedBlock_;
	ChainsIdxs activeChains_;

	State():addedBlock_(invalidIdx()),activeChains_(){;}
	State( Idx bi ):addedBlock_(bi),activeChains_(){;}

	unsigned int nChains() const { return activeChains_.size(); }

	bool contains( const ChainIdxs& c ) const {
		auto it = std::find_if( activeChains_.begin(), activeChains_.end(),ChainIdxsComparison(c));
		return (it!=activeChains_.end());
	}
};

class Sequence{
public:
	typedef std::vector<State> States;
	Sequence():nBfixed_(-1){;}
	void setNBFixed( int nbf ){ nBfixed_=nbf;}

	unsigned int nStates() const { return states_.size(); }
	unsigned int nChains() const { unsigned int n=0; for(unsigned int si=0;si<nStates();++si){ n+=state(si).nChains();} return n; }
	unsigned int addState( const State& s ){ states_.push_back(s); return states_.size()-1; }
	const State& state( Idx si ) const { return states_.at(si); }
	State& state( Idx si ){ return states_.at(si); }

	bool isNew( Idx si, const State::ChainIdxs& ci) const {
		if( si==0 ){ return true; }
		return !(state(si-1).contains(ci));
	}

	bool isNew( Idx si, Idx ci) const {
		return isNew(si,state(si).activeChains_.at(ci));
	}

	bool removedNext( Idx si,  const State::ChainIdxs& ci) const {
		if( si ==static_cast<int>(nStates()) ){ return true; }
		return !(state(si+1).contains(ci));
	}

	bool removedNext( Idx si, Idx ci) const {
		return removedNext(si,state(si).activeChains_.at(ci));
	}

	void clearStates(){ states_.clear(); }

	Idx firstChain( Idx bi){
		for(unsigned int si=0;si<states_.size();++si){
			const State& s=state(si);
			for(unsigned int ci=0;ci<s.activeChains_.size();++ci){
				if(s.activeChains_.at(ci).bi_ == bi){
					return s.activeChains_.at(ci).ai_;
				}
			}
		}
		return -1;
	}

	unsigned int chainsAdded( Idx si ) const{
		unsigned int ca=0;
		const State& s =state(si);
		for(unsigned int ci=0;ci<s.activeChains_.size();++ci){
			if(isNew(si,ci)){ ++ca; }
		}
		return ca;
	}

	Statistics chainsAdded() const{
		Statistics s;
		for(unsigned int si=nBfixed_;si<states_.size();++si){
			s.add(chainsAdded(si));
		}
		return s;
	}

	unsigned int chainsRemovedNext( Idx si ) const{
		if(si<0){ return 0; }//first block cannot remove anything
		unsigned int cr=0;
		const State& s =state(si);
		for(unsigned int ci=0;ci<s.activeChains_.size();++ci){
			if(removedNext(si,ci)){ ++cr; }
		}
		return cr;
	}

	Statistics chainsRemovedNext() const{
		Statistics s;
		for(unsigned int si=nBfixed_;si<states_.size();++si){
			s.add(chainsRemovedNext(si));
		}
		return s;
	}

	Statistics chainChanges() const{
		Statistics s;
		for(unsigned int si=nBfixed_;si<states_.size();++si){
			s.add(chainsAdded(si)+chainsRemovedNext(si-1));
		}
		return s;
	}
	
	Statistics chainsTotal() const{
		Statistics s;
		for(unsigned int si=nBfixed_;si<states_.size();++si){
			s.add(states_.at(si).nChains());
		}
		return s;
	}

	void stats( std::ostream& out ) const{
		SelfAssembly::Statistics scc=chainChanges();
		SelfAssembly::Statistics sct=chainsTotal();
		out << sct.avg() <<" & "<< sct.min  <<" & "<< sct.max <<" & ";
		out << scc.avg() <<" & "<< scc.min  <<" & "<< scc.max;
	}

	friend std::ostream& operator<<( std::ostream& out, const Sequence& s ){
		for(unsigned int si=0;si<s.states_.size();++si){
			const State& st=s.state(si);
			out << st.addedBlock_ << " ";
			out << st.activeChains_.size();
			for(unsigned int ci=0;ci<st.activeChains_.size();++ci){
				const State::ChainIdxs& c = st.activeChains_.at(ci);
				out << " " << c.ai_ << " " << c.bi_;
			}
			out << std::endl;
		}
		return out;
	}

	/* //Todo
	friend std::ostream& operator >> ( std::ostream& out, const Sequence& s ){
		for(unsigned int si=0;si<s.states_.size();++si){
			const State& st=s.state(si);
			out << st.addedBlock_ << " ";
			out << st.activeChains_.size();
			for(unsigned int ci=0;ci<st.activeChains_.size();++ci){
				const State::ChainIdxs& c = st.activeChains_.at(ci);
				out << " " << c.ai_ << " " << c.bi_ << " ";
			}
			out << std::endl;
		}
		return out;
	}*/

	/* //Todo: Load verbose file
*/

private:
	States states_;
	int nBfixed_;
};
}

#endif // SEQUENCE_H
