/***************************************************************************
 * chordanalyzer.cpp: implementation of ChordAnalyzer class
 *
 * This file is part of KTabEdit, a KDE tabulature editor
 *
 * copyright (C) 2003-2006 the KTabEdit development team <mrgwen@gmail.com>
 * copyright (C) 2002-2003 the KGuitar development team <mrgwen@gmail.com>
 ***************************************************************************/

/***************************************************************************
 * 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.
 *
 * See the file COPYING for more information.
 ***************************************************************************/

#include "chordanalyzer.h"

#include <KLocale>

ChordAnalyzer::ChordAnalyzer(const QString& name)
: mName(name)
{
	// Memorize mName
	mName = mName.replace(" ", "").replace("(", "").replace(")", "").upper();

	// Reset the steps info
	for (int i = 0; i < 6; i++) {
		mStep[i] = 0;
		mFixed[i] = false;
	}
}

ChordAnalyzer::~ChordAnalyzer()
{

}

bool ChordAnalyzer::analyze()
{
	// C  Db D	Eb E  F	 F# G  G# A	 Bb B
	// 0  1	 2	3  4  5	 6	7  8  9	 10 11

	QChar tc = mName[0];

	// Get tonic
	switch (tc.toAscii())  {
	case 'C': mTonic = 0; break;
	case 'D': mTonic = 2; break;
	case 'E': mTonic = 4; break;
	case 'F': mTonic = 5; break;
	case 'G': mTonic = 7; break;
	case 'A': mTonic = 9; break;
	case 'H':
	case 'B': mTonic = 11; break; // GREYFIX: understand B differently if Jazz active
	default:
		mMessage = i18n("Cannot understand tonic from given chord mName.");
		return false;
	}
	
	mPos = 1;
	
	// Try to fix tonic with sharps and flats
	while (mName[mPos] != QChar::null) {
		if (mName[mPos] == 'B')  {
			mTonic = (mTonic - 1) % 12;
		} else if (mName[mPos] == '#')  {
			mTonic = (mTonic + 1) % 12;
		} else {
			break;
		}
		mPos++;
	}

	// Set default steps (major triad)
	mStep[0] = 3;
	mStep[1] = 2;

	// Main analyze loop - ! REMEMBER, UPPERCASE !
	while (mName[mPos] != QChar::null) {
		int oldmPos = mPos;

		// Check "maj7" for dominant seventh
		if (checkNext("MAJ7"))
			if (!setStep(2, 3, "maj7"))
				return false;

		// Check "min" for minor third step
		if (checkNext("MIN"))
			if (!setStep(0, 2, "min"))
				return false;

		// Check "m" for minor third step
		if (checkNext("M"))
			if (!setStep(0, 2, "m"))
				return false;

		// Check "7M" for dominant seventh
		if (checkNext("7M"))
			if (!setStep(2, 3, "7M"))
				return false;

		// Check "7" for minor seventh
		if (checkNext("7"))
			if (!setStep(2, 2, "7"))
				return false;

		// Check "6" for sixth
		if (checkNext("6"))
			if (!setStep(2, 1, "6"))
				return false;

		// Check "sus2" for second step
		if (checkNext("SUS2"))
			if (!setStep(0, 1, "sus2"))
				return false;

		// Check "s2" for second step
		if (checkNext("S2"))
			if (!setStep(0, 1, "s2"))
				return false;

		// Check "sus4" for second step
		if (checkNext("SUS4"))
			if (!setStep(0, 4, "sus4"))
				return false;

		// Check "s4" for second step
		if (checkNext("S4"))
			if (!setStep(0, 4, "s4"))
				return false;

		// Check "no3" for no third step
		if (checkNext("NO3"))
			if (!setStep(0, 0, "no3"))
				return false;

		// Check "no5" for no fifth step
		if (checkNext("NO5"))
			if (!setStep(1, 0, "no5"))
				return false;

		// Check "aug" for augmented chord
		if (checkNext("AUG"))
			if (!setStep(1, 3, "aug"))
				return false;

		// Check "dim"
		if (checkNext("DIM"))  {
			if (!setStep(0, 2, "dim"))
				return false;
			if (!setStep(1, 1, "dim"))
				return false;
		}

		// Check "5" power chord
		if (checkNext("5"))  {
			if (!setStep(0, 0, "5"))
				return false;
			if (!setStep(1, 2, "5"))
				return false;
			if (!setStep(2, 0, "5"))
				return false;
			if (!setStep(3, 0, "5"))
				return false;
			if (!setStep(4, 0, "5"))
				return false;
			if (!setStep(5, 0, "5"))
				return false;
		}

		// Stumbled across unrecognizable symbol
		if (oldmPos == mPos) {
			mMessage = i18n("Cannot understand notation: \"%0\".").arg(mName.mid(mPos));
			return false;
		}
	}

	return true;
}

// Checks if the sample string comes next in the line of analyze
//
bool ChordAnalyzer::checkNext(const QString& sample)
{
	for (uint i = 0; i < sample.length(); i++)
		if (mName[mPos + i] != sample[i])
			return false;
	
	mPos += sample.length();
	return true;
}

// Sets and fixes the step "index" to value "value", if still not
// fixed. If already fixed, bails out complaining about "reason"
//
bool ChordAnalyzer::setStep(int index, int value, const QString& reason)
{
	if (mFixed[index] && mStep[index] != value) {
		mMessage = i18n("Modifier \"%0\" cannot be used here because of previous symbols in chord mName.").arg(reason);
		return false;
	} else {
		mStep[index] = value;
		mFixed[index] = true;
		return true;
	}
}

QString ChordAnalyzer::message() const
{
	return mMessage;
}

int ChordAnalyzer::getStep(uchar i) const
{
	return mStep[i];
}

int ChordAnalyzer::getTonic() const
{
	return mTonic;
}
