// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/main/cpp/util/strings.h"

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>

#include <cassert>

#include "src/main/cpp/util/exit_code.h"

namespace blaze_util {

using std::string;
using std::vector;

static const char kSeparator[] = " \n\t\r";

// # Table generated by this Python code (bit 0x02 is currently unused):
// def Hex2(n):
//   return '0x' + hex(n/16)[2:] + hex(n%16)[2:]
// def IsPunct(ch):
//   return (ord(ch) >= 32 and ord(ch) < 127 and
//           not ch.isspace() and not ch.isalnum())
// def IsBlank(ch):
//   return ch in ' \t'
// def IsCntrl(ch):
//   return ord(ch) < 32 or ord(ch) == 127
// def IsXDigit(ch):
//   return ch.isdigit() or ch.lower() in 'abcdef'
// for i in range(128):
//   ch = chr(i)
//   mask = ((ch.isalpha() and 0x01 or 0) |
//           (ch.isalnum() and 0x04 or 0) |
//           (ch.isspace() and 0x08 or 0) |
//           (IsPunct(ch) and 0x10 or 0) |
//           (IsBlank(ch) and 0x20 or 0) |
//           (IsCntrl(ch) and 0x40 or 0) |
//           (IsXDigit(ch) and 0x80 or 0))
//   print Hex2(mask) + ',',
//   if i % 16 == 7:
//     print ' //', Hex2(i & 0x78)
//   elif i % 16 == 15:
//     print
const unsigned char kAsciiPropertyBits[256] = {
  0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,  // 0x00
  0x40, 0x68, 0x48, 0x48, 0x48, 0x48, 0x40, 0x40,
  0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,  // 0x10
  0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
  0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,  // 0x20
  0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
  0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84,  // 0x30
  0x84, 0x84, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
  0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05,  // 0x40
  0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
  0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,  // 0x50
  0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x10,
  0x10, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x05,  // 0x60
  0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
  0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,  // 0x70
  0x05, 0x05, 0x05, 0x10, 0x10, 0x10, 0x10, 0x40,
};


bool starts_with(const string &haystack, const string &needle) {
  return (haystack.length() >= needle.length()) &&
      (memcmp(haystack.c_str(), needle.c_str(), needle.length()) == 0);
}

bool ends_with(const string &haystack, const string &needle) {
  return ((haystack.length() >= needle.length()) &&
          (memcmp(haystack.c_str() + (haystack.length()-needle.length()),
                  needle.c_str(), needle.length()) == 0));
}

void JoinStrings(
    const vector<string> &pieces, const char delimeter, string *output) {
  bool first = true;
  for (const auto &piece : pieces) {
    if (first) {
      *output = piece;
      first = false;
    } else {
      *output += delimeter + piece;
    }
  }
}

vector<string> Split(const string &contents, const char delimeter) {
  vector<string> result;
  SplitStringUsing(contents, delimeter, &result);
  return result;
}

void SplitStringUsing(
    const string &contents, const char delimeter, vector<string> *result) {
  assert(result);

  size_t start = 0;
  while (start < contents.length() && contents[start] == delimeter) {
    ++start;
  }

  size_t newline = contents.find(delimeter, start);
  while (newline != string::npos) {
    result->push_back(string(contents, start, newline - start));
    start = newline;
    while (start < contents.length() && contents[start] == delimeter) {
      ++start;
    }
    newline = contents.find(delimeter, start);
  }

  // If there is a trailing line, add that.
  if (start != newline && start != contents.size()) {
    result->push_back(string(contents, start));
  }
}

size_t SplitQuotedStringUsing(const string &contents, const char delimeter,
                              std::vector<string> *output) {
  size_t len = contents.length();
  size_t start = 0;
  size_t quote = string::npos;  // quote position
  size_t num_segments = 0;

  for (size_t pos = 0; pos < len; ++pos) {
    if (start == pos && contents[start] == delimeter) {
      ++start;
    } else if (contents[pos] == '\\') {
      ++pos;
    } else if (quote != string::npos && contents[pos] == contents[quote]) {
      quote = string::npos;
    } else if (quote == string::npos &&
               (contents[pos] == '"' || contents[pos] == '\'')) {
      quote = pos;
    } else if (quote == string::npos && contents[pos] == delimeter) {
      output->push_back(string(contents, start, pos - start));
      start = pos + 1;
      num_segments++;
    }
  }

  // A trailing element
  if (start < len) {
    output->push_back(string(contents, start));
    num_segments++;
  }
  return num_segments;
}

void Replace(const string &oldsub, const string &newsub, string *str) {
  size_t start = 0;
  // This is O(n^2) (the complexity of erase() is actually unspecified, but
  // usually linear).
  while ((start = str->find(oldsub, start)) != string::npos) {
    str->erase(start, oldsub.length());
    str->insert(start, newsub);
    start += newsub.length();
  }
}

void StripWhitespace(string *str) {
  int str_length = str->length();

  // Strip off leading whitespace.
  int first = 0;
  while (first < str_length && ascii_isspace(str->at(first))) {
    ++first;
  }
  // If entire string is white space.
  if (first == str_length) {
    str->clear();
    return;
  }
  if (first > 0) {
    str->erase(0, first);
    str_length -= first;
  }

  // Strip off trailing whitespace.
  int last = str_length - 1;
  while (last >= 0 && ascii_isspace(str->at(last))) {
    --last;
  }
  if (last != (str_length - 1) && last >= 0) {
    str->erase(last + 1, string::npos);
  }
}

static void GetNextToken(const string &str, const char &comment,
                  string::const_iterator *iter, vector<string> *words) {
  string output;
  auto last = *iter;
  char quote = '\0';
  // While not a delimiter.
  while (last != str.end() && (quote || strchr(kSeparator, *last) == nullptr)) {
    // Absorb escapes.
    if (*last == '\\') {
      ++last;
      if (last == str.end()) {
        break;
      }
      output += *last++;
      continue;
    }

    if (quote) {
      if (*last == quote) {
        // Absorb closing quote.
        quote = '\0';
        ++last;
      } else {
        output += *last++;
      }
    } else {
      if (*last == comment) {
        last = str.end();
        break;
      }
      if (*last == '\'' || *last == '"') {
        // Absorb opening quote.
        quote = *last++;
      } else {
        output += *last++;
      }
    }
  }

  if (!output.empty()) {
    words->push_back(output);
  }

  *iter = last;
}

void Tokenize(const string &str, const char &comment, vector<string> *words) {
  assert(words);
  words->clear();

  string::const_iterator i = str.begin();
  while (i != str.end()) {
    // Skip whitespace.
    while (i != str.end() && strchr(kSeparator, *i) != nullptr) {
      i++;
    }
    if (i != str.end() && *i == comment) {
      break;
    }
    GetNextToken(str, comment, &i, words);
  }
}


// Evaluate a format string and store the result in 'str'.
void StringPrintf(string *str, const char *format, ...) {
  assert(str);

  // Determine the required buffer size. vsnpritnf won't account for the
  // terminating '\0'.
  va_list args;
  va_start(args, format);
  int output_size = vsnprintf(nullptr, 0, format, args);
  if (output_size < 0) {
    fprintf(stderr, "Fatal error formatting string: %d", output_size);
    exit(blaze_exit_code::INTERNAL_ERROR);
  }
  va_end(args);

  // Allocate a buffer and format the input.
  int buffer_size = output_size + sizeof '\0';
  char *buf = new char[buffer_size];
  va_start(args, format);
  int print_result = vsnprintf(buf, buffer_size, format, args);
  if (print_result < 0) {
    fprintf(stderr, "Fatal error formatting string: %d", print_result);
    exit(blaze_exit_code::INTERNAL_ERROR);
  }
  va_end(args);

  *str = buf;
  delete[] buf;
}

void ToLower(string *str) {
  assert(str);
  if (str->empty()) {
    return;
  }

  string temp = "";
  for (auto ch : *str) {
    temp += tolower(ch);
  }
  *str = temp;
}

}  // namespace blaze_util
