// ==========================================================================
// Author:  Yee Hsu
// Date:    9/20/2013
// File:    String.hpp
//
// Desc:    String class that inherits from std::string. This is a header
//      only file and can be used standalone. The String class
//      implements many useful string functions not existing in the
//      std::string library.
// ==========================================================================

#ifndef __STRING_HPP__
#define __STRING_HPP__

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <algorithm>

namespace TeraCode
{
    class String;
    typedef std::vector<String> svector;
    typedef unsigned int uint;

    class String : public std::string {
    public:

        // String Constructors
        String() : std::string() { }
        String( const std::string& s ) : std::string( s ) { }
        String( const std::string& s, std::size_t n ) : std::string( s,n ) { }
        String( const char *s, std::size_t n ) : std::string( s,n ) { }
        String( const char *s ) : std::string( s ) { }
        String( std::size_t n, char c ) : std::string ( n,c ) { }
        template <class InputIterator>
        String( InputIterator _f, InputIterator _l ) : std::string( _f,_l ) { }
        String& String::Clone() { return *this; }
        // No need Destructor, in fact, do not implement it, since std::string Destructor is non-virtual

        // convert String into a number
        template <typename T>
        T Convert() { 
            T _r = 0;
            if(isint() or isfloat()) {
                std::stringstream ss;
                ss << *this; ss >> _r;
            } return _r;
        }

        // trim left and right
        String String::Trim(char _s = ' ') {
            TrimLeft(_s); TrimRight(_s); return *this;
        }

        String String::TrimLeft(char _s = ' ') {
            char* _o = const_cast<char*>(c_str());
            char *p = _o; int _t = 0;
            do { if (*_o != _s || _t) { _t = 1; *p++ = *_o; } }
            while (*_o++ != 0); return *this;
        }

        String String::TrimRight(char _s = ' ') {
            char* _o = const_cast<char*>(c_str());
            size_t _l = strlen(_o);
            while (*_o && _l) {
                char* _e = _o + _l-1;
                if( _s == *_e) *_e = 0; else break;
                _l = strlen(_o);
            } return *this;
        }

        // take a line and split it into a vector
        svector String::Split(const String& _s, uint _ul) {
            svector _l; size_t _e, _p = 0;
            while(true) {
                _e = find(_s,_p);
                _l.push_back(substr(_p,_e-_p));
                _p = _e + _s.size();
                if(_e == std::string::npos) break;
                if(_ul != 0) { if(_l.size() >= _ul) { _l.push_back(substr(_p)); break; } }  
            } return _l;
        }

        // append a svector to the string
        String String::JoinVector(const String& _s, svector& _l) {
            for(uint i = 0; i < _l.size(); i++) { append(_s); append(_l[i]);
            } return *this;
        }

        svector String::Tokenize(const String& _d = " ") {
            svector _t; std::string::size_type _l = 0;
            std::string::size_type _c = find_first_of(_d);
            while (_c != npos) {
                _t.push_back(substr(_l, _c-_l));
                _l = _c+1; _c = find_first_of(_d, _l);
            }
        }

        int String::HashCode() {
            int _h = 0; int _l = length();  
            for (int i = 0; i < _l; i++) _h = 31 * _h + at(i);  
            return _h;
        }

        // counts how many times it appears
        uint String::Count(const String& _s) const {
            uint _t = 0; size_t _e,_p = 0;
            while(true) { _e = find(_s,_p); if(_e == std::string::npos) break; _p = _e + _s.size(); _t++; }
            return _t;
        }

        bool String::Equals(const String& _s, bool _case = false) {
            if (_case) { String _t = *this; String _c = _s; 
                return _t.ToLower() == _c.ToLower();
            } return *this == _s;
        }

        bool String::StartsWith(const String& _s) {
            if (_s.size() > size()) return false;
            return std::equal( _s.begin(), _s.end(), begin() );
        }

        bool String::EndsWith(const String& _s) {
            if (_s.size() > size()) return false;
            return std::equal(begin() + size() - _s.size(), end(), _s.begin());
        }

        bool String::Contains(const String& _s) {
            return find(_s) != std::string::npos;
        }

        String String::InsertBefore(const String& _l, const String _r, bool _all = false) {
            return Replace(_l, _r + _l, _all);
        }

        String String::InsertAfter(const String& _l, const String _r, bool _all = false) {
            return Replace(_l, _l + _r, _all);
        }

        String String::ExtractSubString(const String& _l, const String& _r) {
            size_t _bs  = 0; size_t _be = length();
            if (!_l.empty()) _bs = find(_l) + _l.length();
            if (!_r.empty()) _be = find(_r, _bs);
            String _s = substr(_bs, _be-_bs);
            if (_bs >= 0 && _be >= _bs) return _s; else return "";
        }

        String String::ExtractSubStringReverse(const String& _l, const String& _r) {
            size_t _bs = rfind(_l) + _l.length(); size_t _be = find(_r, _bs);
            String _s = substr(_bs, _be-_bs);
            if (_bs >= 0 && _be >= _bs) return _s; else return "";
        }

        String String::RemoveSubString(const String& _s) {
            std::string _r = *this; std::string::size_type _p;
            while( (_p = _r.find( _s )) != _r.npos )
                _r = _r.erase( _p, _s.size() );
            return _r;
        }

        String String::Replace(const String& _s, const String& _r, bool _all = false) {
            size_t _p = 0; while ((_p = find(_s, _p)) != std::string::npos) {
                replace(_p, _s.length(), _r); _p += _r.length(); if (!_all) break;
            } return *this;
        }

        bool String::IsAlpha() const {
            if (empty()) return false;
            for (std::string::size_type i=0; i<length(); ++i) {
                if (!::isalpha((*this)[i])) return false;
            } return true;
        }

        bool String::IsAlphaNumeric() const {
            if (empty()) return false;
            for (std::string::size_type i=0; i<length(); ++i) {
                if (!::isalnum((*this)[i])) return false;
            } return true;
        }

        bool String::IsLower() const {
            if (empty()) return false;
            for (std::string::size_type i=0; i<length(); ++i) {
                if (!::islower((*this)[i])) return false;
            } return true;
        }

        bool String::IsUpper() const {
            if (empty()) return false;
            for (std::string::size_type i=0; i<length(); ++i) {
                if (!::isupper((*this)[i])) return false;
            } return true;
        }

        bool String::IsSpace() const {
            if (empty()) return false;
            for (std::string::size_type i=0; i<length(); ++i) {
                if (!::isspace((*this)[i])) return false;
            } return true;
        }

        bool String::IsInteger() const {
            if (empty()) return false;
            return find_first_not_of("0123456789") == std::string::npos;
        }

        bool String::IsFloat() const {
            if (empty()) return false;
            if (find_first_not_of("0123456789.") == std::string::npos) {
                std::string::size_type _p = find("."); std::string::size_type _r = rfind(".");
                if (_p == _r && _p != std::string::npos) return true;
            } return false;
        }

        bool String::ContainDigit() const {
            if (empty()) return false;
            if (std::string::npos != find_first_of("0123456789")) return true;
            else return false;  // C++11: return std::any_of(s.begin(), s.end(), ::isdigit);
        }

        String String::ToLower() {
            std::transform(begin(), end(), begin(), ::tolower);
            return *this;
        }

        String String::ToUpper() {
            std::transform(begin(), end(), begin(), ::toupper);
            return *this;
        }

        String String::Format(size_t sz, const char* _f, ...) {
            va_list _arg; va_start(_arg, _f);  char* _s = new char[sz];
            if (_s) {
                memset(_s, 0, sz); vsnprintf(_s, sz, _f, _arg);
                *this = _s; delete [] _s;
            } va_end(_arg); return *this;
        }

        // BAD, obsolete...
        String String::Format(const char* _f, ...) {
            char *s, ch=0; int n, i=0, m; long l; double d;
            std::string sf = _f; std::stringstream ss;
            va_list _arg; va_start(_arg, _f); m = sf.length();
            while (i<m) { ch = sf.at(i); if (ch == '%') { i++; if (i<m) { ch = sf.at(i); switch(ch) {
                case 's': { s = va_arg(_arg, char*);  ss << s;         } break;
                case 'c': { n = va_arg(_arg, int);    ss << (char)n;   } break;
                case 'd': { n = va_arg(_arg, int);    ss << (int)n;    } break;
                case 'l': { l = va_arg(_arg, long);   ss << (long)l;   } break;
                case 'f': { d = va_arg(_arg, double); ss << (float)d;  } break;
                case 'e': { d = va_arg(_arg, double); ss << (double)d; } break;
                case 'X':
                case 'x':
                    {
                        if (++i<m)
                        {
                            ss << std::hex    << std::setiosflags (std::ios_base::showbase);
                            if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                            char ch2 = sf.at(i);
                            if    (ch2 == 'c') { n = va_arg(_arg, int);  ss << std::hex << (char)n; }
                            else if (ch2 == 'd') { n = va_arg(_arg, int);  ss << std::hex << (int) n; }
                            else if (ch2 == 'l') { l = va_arg(_arg, long);  ss << std::hex << (long)l; }
                            else ss << '%' << ch << ch2;
                            ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                        }
                    } break;
                case '%': { ss << '%'; } break;
                default:  { ss << "%" << ch; /* i = m; //get out of loop */ }
                } } }  else ss << ch; i++; } va_end(_arg); *this = ss.str(); return *this;
        }
    };
}

#endif