<?php
###############################################################################
# WhoisParser.php
#
# @author Anil Kumar <akumar@codepunch.com>
# @link   https://codepunch.com
#
############################################################################### 

namespace 	CodePunch\LU;
use CodePunch\Base\Util as UTIL;

###############################################################################

class WhoisParser  {
	
	private $whoisserver = null;
	private $whoisdata = null;
	private $database = null;
	
	###########################################################################
	
	public function __construct($db, $wd, $server)
	{
		$this->setData($wd, $server);
		$this->database = $db;
	}
	
	###########################################################################
	
	public function setData($wd, $server)
	{
		$this->whoisserver = trim($server);
		$this->whoisdata = trim($wd);
	}
	
	###########################################################################
	
	function postProcess($domain)
	{
		$data_array = array();
		$deeplookup = false;	
		
		$parserfn = str_replace(".", "_", $this->whoisserver . "_parser");
		if(function_exists($parserfn))
			$data_array = $parserfn($domain, $this->whoisdata);
		else
			$data_array = $this->genericRegistryParser();
		return $data_array;
	}
	
	###############################################################################

	public function processRegistrarData($domain)
	{
		$data_array = array();
		$parserfn = str_replace(".", "_", $this->whoisserver . "_parser");
		if(function_exists($parserfn))
			$data_array = $parserfn($domain, $whoisdata);
		else
			$data_array = $this->genericRegistrarParser();
		return $data_array;
	}
	
	###########################################################################
	
	public function normalizeDateString($datestring) {
		if (!is_string($datestring)) {
			return $datestring;
		}

		// Remove ordinal suffixes from day numbers (1st, 2nd, 3rd, 4th, etc.)
		$datestring = preg_replace(
			'/\b(\d{1,2})(st|nd|rd|th)\b/i',
			'$1',
			$datestring
		);

		// Replace the word "at" with a space
		$datestring = preg_replace(
			'/\bat\b/i',
			' ',
			$datestring
		);

		// Normalize whitespace
		$datestring = preg_replace('/\s+/', ' ', trim($datestring));

		return $datestring;
	}
	
	###########################################################################
	
	public function preprocessAnnualDates(string $input, ?\DateTime $now = null): string {
		if ($now === null) {
			$now = new \DateTime("now");
		}

		$pattern = '/\b(\d{1,2})(st|nd|rd|th)?\s+([a-zA-Z]+)\s+(each|every)\s+year\b/i';

		$months = [
			'january' => 1,  'jan' => 1,
			'february' => 2, 'feb' => 2,
			'march' => 3,    'mar' => 3,
			'april' => 4,    'apr' => 4,
			'may' => 5,
			'june' => 6,     'jun' => 6,
			'july' => 7,     'jul' => 7,
			'august' => 8,   'aug' => 8,
			'september' => 9,'sep' => 9,
			'october' => 10, 'oct' => 10,
			'november' => 11,'nov' => 11,
			'december' => 12,'dec' => 12
		];

		return preg_replace_callback($pattern, function ($m) use ($months, $now) {
			$day = (int) $m[1];
			$monthName = strtolower($m[3]);

			if (!isset($months[$monthName])) {
				return $m[0];
			}

			$year = (int) $now->format('Y');

			$candidate = \DateTime::createFromFormat(
				'Y-n-j',
				$year . '-' . $months[$monthName] . '-' . $day
			);
			$candidate->setTime(0, 0, 0);

			if ($candidate < $now) {
				$candidate->modify('+1 year');
			}

			return $candidate->format('j F Y');
		}, $input);
	}

	###########################################################################

	public function getDateFromString($datestring)
	{
		$months = array( 'jan'=>1,  'ene'=>1,  'feb'=>2,  'mar'=>3, 'apr'=>4, 'abr'=>4,
					 'may'=>5,  'jun'=>6,  'jul'=>7,  'aug'=>8, 'ago'=>8, 'sep'=>9,
					 'oct'=>10, 'nov'=>11, 'dec'=>12, 'dic'=>12 );
					 
		$datestring = $this->normalizeDateString($datestring);
		$datestring = $this->preprocessAnnualDates($datestring);
		 
		$realformat = "";
		$format = "";
		if($this->whoisserver != "" && $this->database)
			$format = $this->database->findOneOf($this->database->getWhoisServersTableName(), "server", $this->whoisserver, "dateformat");
		if($format == "auto" || is_null($format) || $format == "null" || $format === false)
			$format = "";
			
		$year = 0;
		$month = 0;
		$date = 0;
		$candidates = array();
					 
		$datestring = $this->remove_junk_from_date_string(trim($datestring));
		$datestring = str_replace(',',' ', $datestring);
		$datestring = str_replace('.',' ', $datestring);
		$datestring = str_replace('-',' ', $datestring);
		$datestring = str_replace('/',' ', $datestring);
		$datestring = str_replace("\t",' ', $datestring);
		
		$parts = explode(' ',$datestring);
		$pcount = count($parts);
		$i = 0;
		$pyyyymmdd = 0;
		for($i = 0; $i < $pcount; $i++)
		{
			$entry = $parts[$i];
			
			// If the entry consists of 'n'th, 'n'rd 'n'st, strip off the 'th, st, rd' (4th May, 1st January, etc)
			$elen = strlen($entry);
			if($elen >= 3)
			{		
				$pf = strtolower(substr($entry, $elen-2, 2));
				if($pf == "rd" || $pf == "st" || $pf == "th")
				{
					$prf = substr($entry, 0, $elen-2);
					if(is_numeric($prf))
						$entry = $prf;
				}
			}
			
			if(!is_numeric($entry) && substr($entry, 2, 1) == "T" && substr($entry, 5, 1) == ":" && substr($entry, 8, 1) == ":")
				$entry = substr($entry, 0, 2);
				
			// Jan 08 2014 
			// Fix for Thu Apr 27 00:00:00 EDT 2023T00:00:00Z
			if(!is_numeric($entry) && strlen($entry) > 4 && substr($entry, 4, 1) == "T" && is_numeric(substr($entry,0,4)))
				$entry = substr($entry, 0, 4);
				
			// Dec 12 2013
			// Fix for "2003/11/29 10:03:20+02"
			if(substr($entry, 0, 1) == "+" || substr($entry, 0, 1) == ".")
				continue;
			// 
		
			if(!is_numeric($entry))
			{
				if(isset($months[substr(strtolower($entry),0,3)]))
				{
					$month = $months[substr(strtolower($entry),0,3)];
					continue;
				}
			}
			else if($entry > 1900 && $entry < 2300)
			{
				$year = $entry;
				// Jan 2013
				if($month == 0 && $date == 0 && $pyyyymmdd == 0 && $format == "")
					$pyyyymmdd = 1;
				//
			}
			else if($entry >= 13 && $entry <= 31 && $date == 0 && $year > 1970)
				$date = $entry;
			else if($month >= 1 && $month <= 12 && $date == 0 && $entry <= 31 && $entry >= 1 && $year > 1970)
				$date = $entry;
			else if($date >= 1 && $date <= 31 && $month == 0 && $entry >= 1 && $entry <= 12)
				$month = $entry;
			// March 2012
			else if($entry > 70 && $entry <= 99 && $year == 0)
				$year = 1900+$entry;
			else if($entry == 0 && $year == 0)
				$year = 2000;
			//
			// Jan 2013
			else if($pyyyymmdd == 1 && $entry >= 1 && $entry <= 12 && $month == 0 && $date == 0)
			{
				$pyyyymmdd = 2;
				$candidates[0] = $entry;
			}
			else if($pyyyymmdd == 2 && $entry >= 1 && $entry <= 31 && $month == 0 && $date == 0)
			{
				$pyyyymmdd = 3;
				$month = $candidates[0];
				$date = $entry;
			}
			//
			else
			{
				if(strlen($entry) <= 4)
					$candidates[] = $entry;
			}
		}
		
		foreach($candidates as $key=>$val)
		{
			if($month >= 1 && $month <= 12 && $date == 0 && $val <= 31 && $val >= 1 && $year > 1970)
				$date = $val;
			else if($date >= 1 && $date <= 31 && $month == 0 && $val >= 1 && $val <= 12)
				$month = $val;
		}
		
		if($month == 0 && $date == 0 && count($candidates) == 2)
		{
			if($format == "mmdd")
			{
				if($candidates[0] >= 1 && $candidates[0] <= 12)
					$month = $candidates[0];
				if($candidates[1] >= 1 && $candidates[1] <= 31)
					$date = $candidates[1];
			}
			else if($format == "ddmm")
			{
				if($candidates[1] >= 1 && $candidates[1] <= 12)
					$month = $candidates[1];
				if($candidates[0] >= 1 && $candidates[0] <= 31)
					$date = $candidates[0];
			}
			// Added June 2014
			else if($candidates[0] > $candidates[1] && $candidates[0] > 12 && $candidates[0] <= 31 && $candidates[1] >= 1 && $candidates[1] <= 12 && $year > 1970)
			{
				// 29-8-2014
				$month = $candidates[1];
				$date = $candidates[0];
			}
			else if($candidates[1] > $candidates[0] && $candidates[1] > 12 && $candidates[1] <= 31 && $candidates[0] >= 1 && $candidates[0] <= 12 && $year > 1970)
			{
				// 8-29-2014
				$month = $candidates[0];
				$date = $candidates[1];
			}
		}
		
		// March 2012
		if($year == 0 && $date == 0 && count($candidates) == 2)
		{
			if($format == "ddyy" || $format == "ddmmyy")
			{
				if($candidates[0] >= 1 && $candidates[0] <= 31)
					$date = $candidates[0];
				if(($candidates[1] >= 1 && $candidates[1] <= 30) || $candidates[1] == 0)
				{
					$year = $candidates[1] + 2000;
				}
				else if($candidates[1] >= 70 && $candidates[1] <= 99)
				{
					$year = $candidates[1] + 1900;
				}
				else if($candidates[1] >= 1970 && $candidates[1] <= 2300)
				{
					$year = $candidates[1];
				}
			}
			else if($format == "yydd" || $format == "yymmdd")
			{
				if($candidates[1] >= 1 && $candidates[1] <= 31)
					$date = $candidates[1];
				if(($candidates[0] >= 1 && $candidates[0] <= 30) || $candidates[0] == 0)
				{
					$year = $candidates[0] + 2000;
				}
				else if($candidates[0] >= 70 && $candidates[0] <= 99)
				{
					$year = $candidates[0] + 1900;
				}
				else if($candidates[0] >= 1970 && $candidates[0] <= 2300)
				{
					$year = $candidates[0];
				}
			}
		}

		// TODO
		//if($realformat != "" && $format == "" && $server != "")
		//	set_dateformat_for_whoisserver($server, $realformat);
		
		// Oct 2015
		// Dates like (BR Domains)
		// created: 20051111 #2459220 
		// expires: 20191111 
		// changed: 20141028 
		if($month == 0 && $date == 0 && $year == 0 && $pcount <= 2)
		{
			if($pcount == 2 && strlen($datestring) > 10)
			{
				// created: 20051111 #2459220 
				if(substr($datestring, 8, 2) == " #")
					$datestring = substr($datestring, 0, 8);
			}
			if(strlen($datestring) == 8 && ctype_digit($datestring))
			{
				$year = substr($datestring, 0, 4);
				if($year > 1990 && $year < 2300)
				{
					$month = substr($datestring, 4, 2);
					$date = substr($datestring, 6, 2);
				}
			}
		}
		//

		// March 2012
		if($year < 1900 || $month <= 0 || $date <= 0 || $month > 12 || $date > 31)
			return "";
		// 
		
		return sprintf("%.4d-%02d-%02d",$year,$month,$date);
	}
	
	###############################################################################

	public function remove_junk_from_date_string($date)
	{
		$wnames = array( 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday',
							'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', 'gmt', 
					);			
		foreach($wnames as $k => $v) {
			$date = str_ireplace($v, "", $date);
		}
		return $date;
	}

	
	###############################################################################

	public function get_default_translation_array($nullkeys="")
	{
		$translate = array(
			'Whois Server:' => 'secondary_whois_server',
			'WHOIS Server:' => 'secondary_whois_server',
			'Registrar WHOIS Server:' => 'secondary_whois_server',
			'Domain ID' => 'domain_id',
			'Registry Domain ID:' => 'domain_id',
			
			'Domain Status' => 'status',
			'Status:' => 'status',
			'DomainStatus:' => 'status',
			'Domain Status:' => 'status',
			'Estado / Status:' => 'status',
			'registration status:' => 'status',
			//'Domain Status:{ml}' => 'status',
			
			'Sponsoring Registrar' => 'registrar',
			'Registrar Name:' => 'registrar',
			'Registrar:' => 'registrar',
			'registrar name:' => 'registrar',
			'Registrar:{nl}' => 'registrar',
			
			'Registrant Name' => 'owner',
			'Domain Owner{nl}' => 'owner',
			'Registrant:' => 'owner',
			'Registrant:{nl}' => 'owner',
			'Titular / Registrant{nl}' => 'owner',
			
			'Registrant Organization' => 'organization',
			'Registered By{nl}' => 'organization',
			
			'Registrant Country Code' => 'owner_country',
			'Registrant Country Code' => 'owner_country',
			'Registrant Country/Economy:' => 'owner_country',
			
			'Registrant Address:' => 'address',
			'Registrant Address1' => 'address',
			'Registrant Address2' => 'address',
			'Registrant Address3' => 'address',
			'Registrant Street:' => 'address',
			'Registrant Street1' => 'address',
			'Registrant Street2' => 'address',
			'Registrant Street3' => 'address',
			'Registrant City' => 'address',
			'Registrant State:' => 'address',
			'Registrant State/Province' => 'address',
			'Registrant Postal Code' => 'address',
			'Registrant ZIP' => 'address',
			'Registrant Country' => 'address',
			//'Registrant Address{ml}:' => 'address',
			'Registrant{ml}:' => 'address',
			
			'Administrative Contact Phone Number' => 'admin_phone',
			'Admin Phone' => 'admin_phone',
			'Admin Phone Ext.' => 'admin_phone',
			
			'Administrative Contact Email' => 'admin_email',
			'Admin Email' => 'admin_email',
			'Administrative Email' => 'admin_email',
			'Administrative E-mail' => 'admin_email',
			'Administrative Contact [{nl}' => 'admin_email',
			
			'Registrant Contact Email' => 'registrant_email',
			'Registrant Email' => 'registrant_email',
			'Registrant E-mail' => 'registrant_email',
			'Registrant Contact [{nl}' => 'registrant_email',
			
			'Billing Contact Email' => 'billing_email',
			'Billing Email' => 'billing_email',
			'Billing E-mail' => 'billing_email',
			'Billing Contact [{nl}' => 'billing_email',
			
			'Technical Contact Email' => 'tech_email',
			'Tech Email' => 'tech_email',
			'Technical E-mail' => 'tech_email',
			'Technical Email' => 'tech_email',
			'Technical Contact [{nl}' => 'tech_email',
			
			'Domain Registration Date' => 'created_on',
			'Record created on' => 'created_on',
			'Creation Date' => 'created_on',
			'Creation date' => 'created_on',
			'Created On' => 'created_on',
			'Created on' => 'created_on',
			'Created:' => 'created_on',
			'Created Date' => 'created_on',
			'created:' => 'created_on',
			'Entry created:{nl}' => 'created_on',
			'Registration Date:' => 'created_on',
			'Domain created on' => 'created_on',
			'Record Created on' => 'created_on',
			'Domain Create Date:' => 'created_on',
			'domain_dateregistered:' => 'created_on',
			'Registration Time:' => 'created_on',
			'Registered on:' => 'created_on',
			'Data de registo / Creation Date (dd/mm/yyyy):' => 'created_on',
			'Registered on ' => 'created_on',
			
			'Registrar Expiration Date:' => 'expiry_date',
			'Registrar Registration Expiration Date:' => 'expiry_date',
			'Registrar Registration Expiration date:' => 'expiry_date',
			'Expiration Date:' => 'expiry_date',
			'Domain Currently Expires:' => 'expiry_date',
			'Domain Expiration Date' => 'expiry_date',
			'Record expires on' => 'expiry_date',
			'Domain Expires on' => 'expiry_date',
			'Expiration Date' => 'expiry_date',
			'Expiration date' => 'expiry_date',
			'Expires On' => 'expiry_date',
			'Expires on' => 'expiry_date',
			'Expire on' => 'expiry_date',
			'Expiry Date' => 'expiry_date',
			'Expires' => 'expiry_date',
			'Valid Date' => 'expiry_date',
			'Valid Until:' => 'expiry_date',
			'expire:' => 'expiry_date',
			'expires:' => 'expiry_date',
			'Expire Date:' => 'expiry_date',
			'Record expires:' => 'expiry_date',
			'paid-till' => 'expiry_date',
			'Renewal Date:' => 'expiry_date',
			'Renewal date{nl}' => 'expiry_date',
			'Expiry date:' => 'expiry_date',
			'Domain expires on' => 'expiry_date',
			'Record expired on' => 'expiry_date',
			'Registry Expiry Date:' => 'expiry_date',
			'domain_datebilleduntil:' => 'expiry_date',
			'Expiration Time:'=> 'expiry_date',
			'Data de expiração / Expiration Date (dd/mm/yyyy):' => 'expiry_date',
			'Registry fee due on ' => 'expiry_date',
			
			'Domain Last Updated Date' => 'last_update',
			'Last Updated On' => 'last_update',
			'Last Updated on' => 'last_update',
			'Last updated on' => 'last_update',
			'Record last updated on' => 'last_update',
			'Modified' => 'last_update',
			'Updated Date' => 'last_update',
			'Entry updated:{nl}' => 'last_update',
			'Record last updated at' => 'last_update',
			'domain_datelastmodified:' => 'last_update',
			'Last Update:' => 'last_update',
			'Last updated:' => 'last_update',
			'Last Modified:' => 'last_update',
			'Updated:' => 'last_update',
			
			'Domain servers in listed order{ml}' => 'name server',
			'nserver:' => 'name server',
			'Nserver:' => 'name server',
			'NServer:' => 'name server',
			'Name Server:' => 'name server',
			'[Name Server]' => 'name server',
			'Nameservers:' => 'name server',
			'Nameservers:{ml}' => 'name server',
			'Servers:{ml}' => 'name server',
			'Name servers:{ml}' => 'name server',
			'Name Servers:{ml}' => 'name server',
			'Current Nameservers:{ml}' => 'name server',
			'Nameservers{ml}' => 'name server',
			'Nameserver:' => 'name server',
			'NAME SERVER INFORMATION:{ml}' => 'name server',
			'Name Server 0:' => 'name server',
			'Name Server 1:' => 'name server',
			'Name Server 2:' => 'name server',
			'Name Server 3:' => 'name server',
			'Name Server 4:' => 'name server',
			'Name Server 5:' => 'name server',
			'Name Server 6:' => 'name server',
			'Name Server 7:' => 'name server',
			'Domain nameservers:{ml}' => 'name server',
			
			'Registrant Organization:' => 'registrant_organization',
			'Registrant Street:' => 'registrant_street',
			'Registrant City:' => 'registrant_city',
			'Registrant State:' => 'registrant_state',
			'Registrant State/Province:' => 'registrant_state',
			'Registrant Postal Code:' => 'registrant_postal_code',
			'Registrant Country:' => 'owner_country',
			'Registrant Phone:' => 'registrant_phone',
			'Registrant Fax:' => 'registrant_fax',
			'Admin Name:' => 'admin_name',
			'Admin Organization:' => 'admin_organization',
			'Admin Street:' => 'admin_street',
			'Admin City:' => 'admin_city',
			'Admin State:' => 'admin_state',
			'Admin State/Province:' => 'admin_state',
			'Admin Postal Code:' => 'admin_postal_code',
			'Admin Country:' => 'admin_country',
			'Admin Phone:' => 'admin_phone',
			'Admin Fax:' => 'admin_fax',
			'Tech Name:' => 'tech_name',
			'Tech Organization:' => 'tech_organization',
			'Tech Street:' => 'tech_street',
			'Tech City:' => 'tech_city',
			'Tech State:' => 'tech_state',
			'Tech State/Province:' => 'tech_state',
			'Tech Postal Code:' => 'tech_postal_code',
			'Tech Country:' => 'tech_country',
			'Tech Phone:' => 'tech_phone',
			'Tech Fax:' => 'tech_fax'
		);
		
		$gtrtable = $this->database->getParserTableFor("*");
		if($gtrtable !== false && is_array($gtrtable)) {
			foreach($gtrtable as $key=>$val)
				$translate[$key] = $val;
		}
		
		if(is_array($nullkeys)) {
			$rows = count($nullkeys);
			$i = 0;
			for($i = 0; $i < $rows; $i++) {
				if(isset($nullkeys[$i])) {
					foreach($translate as $key=>$val) {
						if($val == $nullkeys[$i])
							unset($translate[$key]);
					}
				}
			}
		}
		return $translate;
	}
	

	function get_ipwhois_translation_array($nullkeys="")
	{
		$translate = array(
			'netname:' => 'ip_net_name',
			'NetName:' => 'ip_net_name',
			'network:Network-Name:' => 'ip_net_name',
			
			'CustName:' => 'ip_org_name',
			'OrgName:' => 'ip_org_name',
			'org-name:' => 'ip_org_name',
			'network:Org-Name:' => 'ip_org_name',
			'mnt-by:' => 'ip_org_name',
			'network:Organization:'=>'ip_org_name',
			'network:Organization;I:'=>'ip_org_name',
			'network:org-name:' => 'ip_org_name',
			'network:OrgName:' => 'ip_org_name',
			
			'RTechName:' => 'ip_rtech_name',
			'contact:Tech-Name:' => 'ip_rtech_name',
			'OrgTechName:' => 'ip_rtech_name'
		);
		
		if(is_array($nullkeys)) {
			$rows = count($nullkeys);
			$i = 0;
			for($i = 0; $i < $rows; $i++) {
				if(isset($nullkeys[$i])) {
					foreach($translate as $key=>$val) {
						if($val == $nullkeys[$i])
							unset($translate[$key]);
					}
				}
			}
		}
		return $translate;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function getParserTable($whoisserver, $isregistrydata=true)
	{
		$translate = $this->database->getParserTableFor($whoisserver);
		if($translate !== false) {
			if(array_key_exists("[*]", $translate)) {
				unset($translate['[*]']);
				$nullkeys = array();
				foreach($translate as $key=>$val)
					$nullkeys[] = $val;
				$translate = array_merge($translate, $this->get_default_translation_array($nullkeys));
			}
		}
		else
			$translate = $this->get_default_translation_array();
		
		foreach($translate as $key => $val) {
			if($val == "expiry_date") {
				if($isregistrydata)
					$translate[$key] = "registry_expiry";
				else
					$translate[$key] = "registrar_expiry";
			}
		}

		return $translate;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	function genericRegistrarParser()
	{
		$wlines = explode("\n", $this->whoisdata);
		$translate = $this->getParserTable($this->whoisserver,false);
		$blocks = $this->genericParser($wlines, $translate);
		$this->formatDates($blocks);
		$data_array = $this->extractDataFromParas($blocks);
		
		$data_array['secondary_whois_checked_at'] = date("Y-m-d H:i:s");
		$data_array['secondary_whois_server'] = $this->whoisserver;
		$rwdata = implode("<br>", $wlines);
		$rwdata = UTIL::toUTF8($rwdata);
		$data_array['registrar_whois'] = $rwdata;
		return $data_array;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	function genericRegistryParser()
	{
		$unreg = false;
		$avtokens = array();
		$avail_token = $this->database->findOneOf($this->database->getWhoisServersTableName(), "server", $this->whoisserver, "searchtoken");
		if($avail_token == "")
			$avail_token = "This domain name has not been registered|Domain not found|No Data Found|No entries found for the selected source|ERROR:101: no entries found|DOMAIN NOT FOUND|Status: Not Registered|Above domain name is not registered|Domain Not Found|Status:	AVAILABLE|is not a registered domain|No domain records were found to match|NO MATCH for domain|% No match|No data was found to match the request criteria|% nothing found|Domain Status: Available|No match for \"|NOT FOUND|\nNo match!!|[BW]No information available about domain name |\nStatus: AVAILABLE|\nStatus: free";
		if($avail_token != "")
			$avtokens = explode("|", $avail_token);
		foreach($avtokens as $token) {
			if($token != "") {
				$startwith = false;
				if(UTIL::starts_with($token, "[BW]")) {
					$token = mb_substr($token, 4);
					$startwith = true;
				}
				$pos = mb_stripos($this->whoisdata, $token);
				if($pos !== false) {
					if(($startwith && $pos == 0) || !$startwith) {
						$unreg = true;
						break;
					}
				}
			}
		}
		if(strlen($this->whoisdata) < 32 && $unreg == false) {
			$szTokens = array("NOT FOUND", "not found", "Not Found", "Not found", "No Data Found", "NO MATCH", "no match", "No Match", "No match", "not found...");
			foreach($szTokens as $szToken) {
				if($this->whoisdata == $szToken) {
					$unreg = true;
					break;
				}
			}
			if($unreg == false) {
				if(stristr($this->whoisdata, "not found") || stristr($this->whoisdata, "no match"))
					$unreg = true;
			}
		}
		
		$wlines = explode("\n", $this->whoisdata);
		
		$data_array = array();
		if($unreg == false) {
			$translate = $this->getParserTable($this->whoisserver, true);
			$blocks = $this->genericParser($wlines, $translate);
			$this->formatDates($blocks);
			$data_array = $this->extractDataFromParas($blocks);
			if($avail_token == "")
				$data_array['availability'] = 'Setup Error';
			else
				$data_array['availability'] = 'Not Available';
		}
		else {
			$data_array['availability'] = 'Available';
			$data_array['status'] = 'Not Registered';
		}
		
		$rwdata = implode("<br>", $wlines);
		$rwdata = UTIL::toUTF8($rwdata);
		$data_array['registry_whois']  = $rwdata;
		$data_array['primary_whois_server'] = $this->whoisserver;
		$data_array['primary_whois_checked_at'] = date("Y-m-d H:i:s");
		return $data_array;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function genericIPWhoisParser()
	{
		$data_array = array();
		$wlines = explode("\n", $this->whoisdata);
		$translate = $this->get_ipwhois_translation_array();
		$blocks = $this->genericParser($wlines, $translate);
		$data_array = $this->extractDataFromParas($blocks);

		$data_array['ip_whois_at'] = date("Y-m-d H:i:s");
		$rwdata = implode("<br>", $wlines);
		$rwdata = UTIL::toUTF8($rwdata);
		$data_array['ip_whois'] = $rwdata;
		return $data_array;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function CleanupStatusText(&$v)
	{
		$idx = strpos($v, "https://www.icann.org/epp");
		if($idx !== false)
			$v = substr($v, 0, $idx);

		$idx = strpos($v, "http://www.icann.org/epp");
		if($idx !== false)
			$v = substr($v, 0, $idx);

		$idx = strpos($v, "http://icann.org/epp");
		if($idx !== false)
			$v = substr($v, 0, $idx);

		$idx = strpos($v, "https://icann.org/epp");
		if($idx !== false)
			$v = substr($v, 0, $idx);
		
		$v = str_ireplace("(Protected by .auLOCKDOWN)", "", $v);

		$v = trim($v, " -()\t\n\r\0\x0B");
		
		if(strtolower($v) == "ext")
			$v = "";
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function extractDataFromParas($blocks)
	{
		$data_array = array();
		$count = count($blocks);
		foreach($blocks as $key=>$value) {
			if(!is_array($value)) {
				if($key == "admin_email" || $key == "billing_email" || $key == "tech_email" || $key == "registrant_email")
					$value = $this->cleanupEmail($value);
				else if($key == "status")
					$this->CleanupStatusText($value);
				if($value != "")
					$data_array[$key] = $value;
			}
			else if($key == "name server") {
				// Handle name server array
				if(is_array($value))
					sort($value);
				$idx = 1;
				foreach($value as $k => $v) 
				{ 
					$field = "ns" . (string)$idx++;
					$data_array[$field] = $this->cleanupNameServer($v);
				}
			}
			else {
				//if($key == "status")
				{
					foreach($value as &$v)
						$this->CleanupStatusText($v);
					$value = array_values(array_unique($value));
				}
				$data_array[$key] = trim(implode(",", $value), ",");
			}
		}
		return $data_array;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function genericParser($rawdata, $translate)
	{
		$blocks = array();
		$iblock = array();
		$akey   = "";
		
		$rows = count($rawdata);
		$i = 0;
		
		foreach($translate as $tk => $tv) {
			$nextline = false;
			$multiline = false;
			$singleindex = -1;
			
			$keystart = "";
			$keyend   = "";
			$startat  = 0;
			$endat    = $rows;
			
			$dtoken = "";
			if(strstr($tk, "^^")) {
				// contains dynamic key.
				// Eg. nic-hdl: ^^holder-c: ^^@@contact:=>owner
				// Anything between ^^ pair is a token.
				// Eg. holder-c:  is the token.
				// we need to replace the ^^holder-c: ^^ with the holder-c entry from $rawdata
				// Support only simple tokens for now.
				$dtoken = UTIL::get_string_between($tk, "^^", "^^");
				if($dtoken != "") {
					$dv = "";
					$dk = trim($dtoken);
					if($dk != "") {
						foreach($rawdata as $rd) {
							if(strstr($rd, $dk)) {
								$dvc = UTIL::get_string_between($rd."\n", $dk, "\n");
								$dvc = trim($dvc);
								if($dvc != "") {
									$dv = $dvc;
									break;
								}
							}
						}
					}
					$tk = str_replace("^^".$dtoken."^^", $dv, $tk);
				}
			}
			if(stristr($tk, "{nl}")) {
				$nextline = true;
				$tk = str_replace("{nl}", "", $tk);
			}
			if(stristr($tk, "{ml}")) {
				$multiline = true;
				$tk = str_replace("{ml}", "", $tk);
			}
			if(preg_match_all("/\\{([0-9]+)\\}/", $tk, $matches)) {
				// Find nth Occurrence (1 based index)
				$singleindex = $matches[1][0] - 1;
				$tk = str_replace($matches[0][0], "", $tk);
			}
			if(stristr($tk, "@@")) {
				// calculate $startat and endat.
				$pos = mb_strpos($tk, "@@");
				$keystart = trim(mb_substr($tk, 0, $pos));
				$tk = trim(mb_substr($tk, $pos+2));
				if(stristr($tk, "@@"))
				{
					$pos = mb_strpos($tk, "@@");
					$keyend = trim(mb_substr($tk, 0, $pos));
					$tk = trim(mb_substr($tk, $pos+2));
				}
				if($keystart != "" && mb_strlen($keystart) > 2 && $keyend == "")
				{
					if($keystart[0] == "[" && $keystart[mb_strlen($keystart)-1] == "]")
						$keyend = "]";
				}
			}
			
			$zonefound = false;
			for($i = $startat; $i < $endat; $i++) {
				$thisrow = trim($rawdata[$i]);
				if($thisrow != '' && ($thisrow == '%' || $thisrow == '#'))
					continue;
					
				if($keystart != "" && $zonefound == false) {
					if(mb_substr($thisrow, 0, mb_strlen($keystart)) != $keystart)
						continue;
					else
						$zonefound = true;
				}
				else if($keyend != "" && $zonefound == true) {
					//if(mb_substr($thisrow, mb_strlen($thisrow)-1, 1) == $keyend)
					if(mb_substr($thisrow, 0, mb_strlen($keyend)) == $keyend)
					{
						$zonefound = false;
						continue;
					}
				}
					
				if(mb_substr($thisrow, 0, mb_strlen($tk)) == $tk) {
					$value = trim(mb_substr($thisrow, mb_strlen($tk)));
					$value = trim($value, ".- :");
					if(substr_count($value, ':') == 1  && !strstr($tk, ':') && substr_count($value, 'http://') == 0 && substr_count($value, 'https://') == 0)
						continue;
					
					$multivalue = array();
					if($multiline && $value != "")
						$multivalue[] = $value;
					if($multiline || $nextline) {
						$scanned_rows = 0;
						while(++$i < $endat) {
							$scanned_rows++;
							$thisvalue = trim($rawdata[$i]);
							$thisvalue = trim($thisvalue, ".- :");
							if($thisvalue == "")
							{
								if($scanned_rows > 1)
									break;
								else
									continue;
							}
								
							if($multiline)
								$multivalue[] = $thisvalue;
							else {
								if($value == "" && $thisvalue != "")
									$value = $thisvalue;
								break;
							}
						}
					}
					
					if(count($multivalue) > 0) {
						$blocks[$tv] = $multivalue;
						unset($multivalue);
						continue;
					}
					
					// Let us remove time strings
					// Things like 21:12:00, GMT etc should go!
					$dateregexp = "/\b(0[0-9]|1[0-9]|2[0-3])(\:)(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])(\:)(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])\b/";
					$value = preg_replace($dateregexp, "", $value); 
					$value = str_ireplace("gmt", "", $value); 
					
					if($value != "") {
						if(isset($blocks[$tv]) && $akey == "") {
							$oldvalue = $blocks[$tv];
							unset($blocks[$tv]);
							
							if(is_array($oldvalue)) {
								foreach($oldvalue as $okey=>$oval)
									$iblock[] = $oval; 
							}
							else
								$iblock[] = $oldvalue; 
							$iblock[] = $value; 
							$akey = $tv;
						}
						else {
							if($akey == $tv) {
								$iblock[] = $value;
							}
							else {
								if(!isset($blocks[$tv]))
									$blocks[$tv] = $value;
								if(count($iblock) > 0 && $akey != "")
									$blocks[$akey] = $iblock;
								$akey = "";
								unset($iblock);
								$iblock = array();
							}
						}
					}
				}
			}
			
			if($akey != "" && count($iblock) > 0) {
				if($singleindex >= 0 && $singleindex < count($iblock))
					$blocks[$akey] = $iblock[$singleindex];
				else
					$blocks[$akey] = $iblock;
				$akey = "";
				unset($iblock);
				$iblock = array();
			}
		}
		
		return $blocks;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	function cleanupEmail($email)
	{
		$cleaned_email = "";
		$parts = explode(" ", $email);
		if(is_array($parts)) {
			foreach($parts as $val) {
				$entry = trim($val);
				if(strpos($entry, "@") === false)
					continue;
				else {
					$cleaned_email = $entry;
					break;
				}
			}
		}
		return $cleaned_email;
	}

	/////////////////////////////////////////////////////////////////////////////////////////////////////////

	public function cleanupNameServer($nameserver)
	{
		$nameserver = str_replace("\t", " ", $nameserver);
		$parts = explode(" ", $nameserver);
		if(is_array($parts))
		{
			foreach($parts as $key=>$val)
			{
				$entry = trim($val);
				$entry = str_replace(".", "", $entry);
				if(is_numeric($entry) || $entry == "")
					unset($parts[$key]);
				else if(substr_count($val, ".") <= 1)
					unset($parts[$key]);
			}
			$nameserver = implode(" ", $parts);
		}
		return strtolower($nameserver);
	}

	###########################################################################

	public function formatDates(&$data_array)
	{
		if(is_array($data_array)) {
			foreach($data_array as $key => $val) {
				$is_a_date = false; 
				if($this->database != null) {
					$cia = $this->database->getDetailsOfDomainColumn($key);
					if($cia !== false) {
						$ft = $cia['fieldtype'];
						if($ft == "date" || $ft == "datetime")
							$is_a_date = true;
					}
				}
				if($key == 'created_on' || $key == 'registry_expiry' || $key == 'registrar_expiry' || $key == 'last_update' || $is_a_date) {
					if(is_array($val)) {
						if(isset($val[0]))
							$data_array[$key] = $this->getDateFromString($val[0]);
					}
					else
						$data_array[$key] = $this->getDateFromString($val);
				}
			}
		}
	}
}

###############################################################################

