//# ParameterParser.h
//# Copyright (C) 2007
//# Associated Universities, Inc. Washington DC, USA.
//#
//# 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 2 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, write to the Free Software Foundation, Inc.,
//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA

#include <imageanalysis/IO/ParameterParser.h>

#include <casacore/casa/Arrays/Vector.h>
#include <casacore/casa/Utilities/Regex.h>
#include <casacore/casa/Utilities/Sort.h>
#include <casacore/measures/Measures/Stokes.h>
#include <casacore/casa/IO/ArrayIO.h>

using namespace casacore;
namespace casa {

vector<uInt> ParameterParser::consolidateAndOrderRanges(
    uInt& nSelected, const vector<uInt>& ranges
) {
    uInt arrSize = ranges.size()/2;
    uInt arrMin[arrSize];
    uInt arrMax[arrSize];
    for (uInt i=0; i<arrSize; i++) {
        arrMin[i] = ranges[2*i];
        arrMax[i] = ranges[2*i + 1];
    }    
    Sort sort;
    sort.sortKey (arrMin, TpUInt);
    sort.sortKey (arrMax, TpUInt, 0, Sort::Descending);
    Vector<uInt> inxvec;
    vector<uInt> consol(0);
    sort.sort(inxvec, arrSize);
    for (uInt i=0; i<arrSize; i++) {
        uInt idx = inxvec(i);
        uInt size = consol.size();
        uInt min = arrMin[idx];
        uInt max = arrMax[idx];
        uInt lastMax = (i == 0) ? 0 : consol[size-1];
        if (i==0) {
            // consol.resize(2, true);
            // consol[0] = min;
            // consol[1] = max;
            consol.push_back(min);
            consol.push_back(max);
        }
        else if ( 
            // overlaps previous range, so extend
            (min < lastMax && max > lastMax)
            // or contiguous with previous range, so extend
            || min == lastMax + 1
        ) {
            // overwriting the end value, so do not resize
            consol[size-1] = max; 
        }

        else if (min > lastMax + 1) { 
            // non overlap of and not contiguous with previous range,
            // so create new end point pair
            consol.push_back(min);
            consol.push_back(max);
        }
    }    
    nSelected = 0; 
    for (uInt i=0; i<consol.size()/2; i++) {
        nSelected += consol[2*i + 1] - consol[2*i] + 1; 
    }    
    return consol;
}
    
std::vector<uInt> ParameterParser::spectralRangesFromChans(
	uInt& nSelectedChannels, const String& specification,  const uInt nChannels
) {
	// First split on commas
	Vector<String> parts = stringToVector(specification, std::regex("[,;]"));
	static const Regex regexuInt("^[0-9]+$");
	static const Regex regexRange("^[0-9]+[ \n\t\r\v\f]*~[ \n\t\r\v\f]*[0-9]+$");
	static const Regex regexLT("^<.*$");
	static const Regex regexLTEq("^<=.*$");
	static const Regex regexGT("^>.*$");
	static const Regex regexGTEq("^>=.*$");
	vector<uInt> ranges(0);

	for (uInt i=0; i<parts.size(); i++) {
		parts[i].trim();
		uInt min = 0;
		uInt max = 0;
		if (parts[i].matches(regexuInt)) {
			// just one channel
			min = String::toInt(parts[i]);
			max = min;
		}
		else if(parts[i].matches(regexRange)) {
			// a range of channels
			Vector<String> values = stringToVector(parts[i], '~');
			ThrowIf(
				values.size() != 2,
				"Incorrect specification for channel range " + parts[i]
			);
			values[0].trim();
			values[1].trim();
			for(uInt j=0; j < 2; j++) {
				ThrowIf(
					! values[j].matches(regexuInt),
					"For channel specification " + values[j]
					+ " is not a non-negative integer in "
					+ parts[i]
				);
			}
			min = String::toInt(values[0]);
			max = String::toInt(values[1]);
		}
		else if (parts[i].matches(regexLT)) {
			String maxs = parts[i].matches(regexLTEq) ? parts[i].substr(2) : parts[i].substr(1);
			maxs.trim();
			ThrowIf(
				! maxs.matches(regexuInt),
				"In channel specification, " + maxs
					+ " is not a non-negative integer in " + parts[i]
			);
			min = 0;
			max = String::toInt(maxs);
			if (! parts[i].matches(regexLTEq)) {
				ThrowIf(
					max == 0,
					"In channel specification, max channel cannot "
					"be less than zero in " + parts[i]
				);
				max--;
			}
		}
		else if (parts[i].matches(regexGT)) {
			String mins = parts[i].matches(regexGTEq)
	         	? parts[i].substr(2)
	         	: parts[i].substr(1);
			mins.trim();
			ThrowIf(
				! mins.matches(regexuInt),
				"In channel specification, " + mins
					+ " is not an integer in " + parts[i]
			);
			max = nChannels - 1;
			min = String::toInt(mins);
			if(! parts[i].matches(regexGTEq)) {
				min++;
			}
			ThrowIf(
				min > nChannels - 1,
				"Min channel cannot be greater than the (zero-based) number of channels ("
				+ String::toString(nChannels - 1) + ") in the image"
			);
		}
		else {
			ThrowCc(
				"Invalid channel specification in " + parts[i]
			    + " of spec " + specification
			);
		}
		ThrowIf(
			min > max,
			"Min channel " + String::toString(min) + " cannot be greater than max channel "
			+ String::toString(max) + " in " + parts[i]
		);
		ThrowIf(
			max >= nChannels,
			"Zero-based max channel " + String::toString(max)
			+ " must be less than the total number of channels ("
			+ String::toString(nChannels) + ") in the channel specification " + parts[i]
		);
		ranges.push_back(min);
		ranges.push_back(max);
	}
	vector<uInt> consolidatedRanges = consolidateAndOrderRanges(nSelectedChannels, ranges);
	return consolidatedRanges;
}

std::vector<String> ParameterParser::stokesFromString(
	String& specification
) {
	specification.trim();
	specification.ltrim('[');
	specification.rtrim(']');
	specification.upcase();

	Vector<String> parts = stringToVector(specification, std::regex("[,;]"));
	Vector<String> polNames = Stokes::allNames(false);
	uInt nNames = polNames.size();
	Vector<uInt> nameLengths(nNames);
	for (uInt i=0; i<nNames; ++i) {
		nameLengths[i] = polNames[i].length();
	}
	uInt *lengthData = nameLengths.data();

	Vector<uInt> idx(nNames);
	Sort sorter;
	sorter.sortKey(lengthData, TpUInt, 0, Sort::Descending);
	sorter.sort(idx, nNames);

	Vector<String> sortedNames(nNames);
	for (uInt i=0; i<nNames; i++) {
		sortedNames[i] = polNames[idx[i]];
		sortedNames[i].upcase();
	}
	vector<String> myStokes;
	for (uInt i=0; i<parts.size(); i++) {
		String part = parts[i];
		part.trim();
		Vector<String>::iterator iter = sortedNames.begin();
		vector<Int> stokes;
		while (iter != sortedNames.end() && ! part.empty()) {
			if (part.startsWith(*iter)) {
				myStokes.push_back(*iter);
				// consume the string
				part = part.substr(iter->length());
				if (! part.empty()) {
					// reset the iterator to start over at the beginning of the list for
					// the next specified polarization
					iter = sortedNames.begin();
				}
			}
			else {
				++iter;
			}
		}
		ThrowIf(
			! part.empty(),
			"(Sub)String " + part + " in stokes specification part " + parts[i]
			    + " does not match a known polarization."
		);
	}
	return myStokes;
}


}