#pragma once
#include <iostream>
#include <map>
#include <vector>

#include "chocolat/list/word_stream.hpp"
#include "chocolat/system/invalid_option_exception.hpp"

#include <string>
#include "debug_macro.hpp"

/**
   input: vector<string> arguments
   string options
   output: vector<string> rest_arguments
   vector<char> accespted_options
   map<char, string> option_arguments
   vector<string> bad_options
*/
class CharacterOptionParser
{
	std::string m_options; //acceptable options.
	WordStream m_arguments;//given command line argument
	typedef std::map<char,std::string>::const_iterator MapIterator;
public:
	std::map<char, std::string> option_arguments;
	std::vector<std::string> rest_arguments;
	std::vector<std::string> bad_options;

	/** This data is pair of option and argument.*/
	class OptionPair
	{
		const MapIterator* m_pi;
	public:
		OptionPair(const MapIterator* i)
        :m_pi(i)
		{
		}
		char option()const
		{
			return (*m_pi)->first;
		}
		std::string argument()const
		{
			return (*m_pi)->second;
		}
		bool isValid()const
		{
			if (m_pi == NULL) {
				return false;
			}
			return true;
		}
	};
	/** option argument iterator.*/
	class iterator
	{
		MapIterator m_iterater;
		OptionPair m_pair;
		void update();
	public:
		iterator(MapIterator i)
        :m_iterater(i), m_pair(&m_iterater)
        {
        }

		iterator(const iterator& that)
        :m_iterater(that.m_iterater), m_pair(&m_iterater)
        {
        }

		
		const OptionPair& operator*()const
        {
            return m_pair;
        }

		const OptionPair* operator->()const
        {
            return &m_pair;
        }

		iterator& operator++()
        {
            ++m_iterater;
            return *this;
        }

		iterator& operator=(std::map<char,std::string>::const_iterator rh)
        {
            m_iterater=rh;
            return *this;
        }

		bool operator==(const iterator& rh)const
        {
            return m_iterater==rh.m_iterater;
        }

		bool operator!=(const iterator& rh)const
        {
            return m_iterater!=rh.m_iterater;
        }

	};
	
	
	iterator begin()const
    {
        return iterator(option_arguments.begin());
    }

	iterator end()const
    {
        return iterator(option_arguments.end());
    }

	CharacterOptionParser(std::string opt)
    		:m_options(opt)
    {
    }

	bool bad()const
    {
        return !bad_options.empty();
    }

	void dump(std::ostream& os)const
    {
        os << "* accepted options and arguments" << std::endl;
        for (std::map<char, std::string>::const_iterator i =
				 option_arguments.begin();
             i!=option_arguments.end();
             ++i){
            os << " -" << i->first << " ";
            os  << i->second << std::endl;
        }
        if (!bad_options.empty()){
            os << "* bad options: ";
            for (unsigned i=0; i<bad_options.size(); ++i){
                os << bad_options[i] << ' ';
            }
            os << std::endl;
        }
        if (!rest_arguments.empty()){
            os << "* rest arguments "<<std::endl;
            for (unsigned i=0; i<rest_arguments.size(); ++i){
                os << rest_arguments[i] << std::endl;
            }
        }
	
    }

	/** parse - Extract command line arguments	 */
	void parse(int argc, char** argv)
    {
        for (int i=1; i<argc;++i){
            m_arguments.push(argv[i]);
        }
        while (!m_arguments.empty()){
            parseArgument();
        }
    }

	void parse(const std::vector<std::string>& arguments)
    {
        for (unsigned i=1; i<arguments.size();++i){
            m_arguments.push(arguments[i]);
        }
        while (!m_arguments.empty()){
            parseArgument();
        }
    }

private:
	bool accepts(std::string argument)
    {
        return (argument[0] == '-'
                && argument.length() >= 2
                && argument[1] != '-');
    }

	void acceptOption(char option)
    {
        acceptArgument(option,"");
    }

	std::string trimInvalidCharacter(std::string argument){
        if (argument[0] == '='){
            return argument.substr(1,std::string::npos);
        } else {
            return argument;
        }
    }

	void acceptArgument(char option, std::string argument)
    {
        option_arguments[option] = trimInvalidCharacter(argument);
    }

	bool hasFollowingArgument()const
    {
        return (!m_arguments.empty() && m_arguments.peek()[0] != '-');
    }

	void parseArgument()
    {
        std::string argument = m_arguments.get();
        if (!accepts(argument)){
            rest_arguments.push_back(argument);
            return;
        } 
        std::string rest(argument.substr(1,std::string::npos));
        while (!rest.empty()){
            char c = rest[0];
            rest = rest.substr(1,std::string::npos);
            if (hasOption(c)){
                if (requiresArgument(c)) {
                    if (!rest.empty()){
                        acceptArgument(c, rest);
                        rest="";
                    } else if (hasFollowingArgument()) {
                        acceptArgument(c, m_arguments.get());
                    } else if (optionalArgument(c)){
                        acceptOption(c);
                    } else {
                        std::string s;
                        s+=c;
                        bad_options.push_back(s);
                    }
                } else {
                    acceptOption(c);
                }
            } else {
                bad_options.push_back(c + rest);
                rest="";
            } 
        }
    }

	bool hasOption(char c)const
    {
        unsigned pos = m_options.find(c);
        return pos != std::string::npos;

    }

	bool requiresArgument(char c)const
    {
        unsigned pos = m_options.find(c);
        return (pos != m_options.length()-1) 
			&&  m_options[pos+1]==':';
    }

	bool optionalArgument(char c)const
    {
        unsigned pos = m_options.find(c);
        return  (pos != m_options.length()-2)
			&& m_options[pos+1]==':'
			&& m_options[pos+2]==':';
    }

};
